mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 12:57:47 +00:00
Compare commits
45 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63cc9009c8 | ||
|
|
ccf6e3c432 | ||
|
|
8d015e3138 | ||
|
|
42f2fa1a20 | ||
|
|
8397aac0e3 | ||
|
|
7bcdc144eb | ||
|
|
624da9170d | ||
|
|
fb5b013a03 | ||
|
|
0a4bb53a58 | ||
|
|
2fbd9c78e3 | ||
|
|
1ed2b7a57d | ||
|
|
88cd9ca3c3 | ||
|
|
ecea2ca4a3 | ||
|
|
3f132bc15b | ||
|
|
61d95094c0 | ||
|
|
f7c47975e3 | ||
|
|
7c96f9f9a5 | ||
|
|
a5c805b6eb | ||
|
|
ff2a22023d | ||
|
|
c6efc028aa | ||
|
|
96565da1cf | ||
|
|
afb5e7fc85 | ||
|
|
34cc987be8 | ||
|
|
55c60d30e6 | ||
|
|
eb993d47ac | ||
|
|
d2184628d1 | ||
|
|
65f0cc6797 | ||
|
|
c628c7b58e | ||
|
|
4e005274f9 | ||
|
|
482373f711 | ||
|
|
c80bb37e8d | ||
|
|
a7acd92ffd | ||
|
|
035720ca82 | ||
|
|
a6ae923a7a | ||
|
|
83c0711d6e | ||
|
|
231f18d56b | ||
|
|
9d73091d8c | ||
|
|
0ca3189f79 | ||
|
|
9ff5bb9cb3 | ||
|
|
45d05a603b | ||
|
|
6ef3b12fde | ||
|
|
b16f94098a | ||
|
|
be315bebcf | ||
|
|
5608a4c42c | ||
|
|
66458fe3e0 |
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,27 +1,27 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @tootallnate
|
||||
* @TooTallNate
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/now-cli/src/commands/dev/ @tootallnate @styfle @AndyBitz
|
||||
/packages/now-cli/src/util/dev/ @tootallnate @styfle @AndyBitz
|
||||
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/certs/ @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/now-cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/now-cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/env @styfle @lucleray
|
||||
/packages/now-client @rdev
|
||||
/packages/now-build-utils @styfle @AndyBitz
|
||||
/packages/now-node @styfle @tootallnate @lucleray
|
||||
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||
/packages/now-client @rdev @styfle @TooTallNate
|
||||
/packages/now-build-utils @styfle @AndyBitz @TooTallNate
|
||||
/packages/now-node @styfle @TooTallNate @lucleray
|
||||
/packages/now-node-bridge @styfle @TooTallNate @lucleray
|
||||
/packages/now-next @Timer @ijjk
|
||||
/packages/now-go @styfle @sophearak
|
||||
/packages/now-python @styfle @sophearak
|
||||
/packages/now-ruby @styfle @coetry @nathancahill
|
||||
/packages/now-go @styfle @TooTallNate
|
||||
/packages/now-python @styfle @TooTallNate
|
||||
/packages/now-ruby @styfle @coetry @TooTallNate
|
||||
/packages/now-static-build @styfle @AndyBitz
|
||||
/packages/now-routing-utils @styfle @dav-is @ijjk
|
||||
/examples @mcsdevv @timothyis
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens
|
||||
/examples/nextjs @timneutkens @Timer
|
||||
/examples/hugo @mcsdevv @timothyis @styfle
|
||||
/examples/jekyll @mcsdevv @timothyis @sarupbanskota
|
||||
/examples/jekyll @mcsdevv @timothyis @styfle
|
||||
/examples/zola @mcsdevv @timothyis @styfle
|
||||
|
||||
14
errors/next-functions-config-optimized-lambdas.md
Normal file
14
errors/next-functions-config-optimized-lambdas.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# `@vercel/next` Functions Config Optimized Lambdas Opt-out
|
||||
|
||||
#### Why This Warning Occurred
|
||||
|
||||
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||
When the `functions` config is added in `now.json` or `vercel.json`, it causes conflicts with this optimization, so it is opted-out.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Remove the `functions` config from your `now.json` or `vercel.json` to take advantage of this optimization.
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Functions Config Documentation](https://vercel.com/docs/configuration?query=functions#project/functions)
|
||||
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# `@vercel/next` Legacy Routes Optimized Lambdas Opt-out
|
||||
|
||||
#### Why This Warning Occurred
|
||||
|
||||
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||
When legacy `routes` are added in `now.json` or `vercel.json`, they cause conflicts with this optimization, so it is opted-out.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Migrate from using legacy `routes` to the new `rewrites`, `redirects`, and `headers` configurations in your `now.json` or `vercel.json` file or leverage them directly in your `next.config.js` with the built-in [custom routes support](https://github.com/zeit/next.js/issues/9081)
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Rewrites Documentation](https://vercel.com/docs/configuration?query=rewrites#project/rewrites)
|
||||
- [Redirects Documentation](https://vercel.com/docs/configuration?query=rewrites#project/redirects)
|
||||
- [Headers Documentation](https://vercel.com/docs/configuration?query=rewrites#project/headers)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.0.15-canary.3",
|
||||
"version": "0.0.15",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
@@ -9,9 +9,9 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "24.0.22",
|
||||
"@types/node": "12.0.4",
|
||||
"ajv": "6.10.2",
|
||||
"ajv": "6.12.2",
|
||||
"jest": "24.9.0",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.5.2"
|
||||
"typescript": "3.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.3.2-canary.2",
|
||||
"version": "2.4.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -45,7 +45,7 @@
|
||||
"node-fetch": "2.2.0",
|
||||
"semver": "6.1.1",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.5.2",
|
||||
"typescript": "3.9.3",
|
||||
"yazl": "2.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: Route[] | null;
|
||||
redirectRoutes: Route[] | null;
|
||||
rewriteRoutes: Route[] | null;
|
||||
errorRoutes: Route[] | null;
|
||||
}> {
|
||||
const errors: ErrorResponse[] = [];
|
||||
const warnings: ErrorResponse[] = [];
|
||||
@@ -99,6 +100,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,6 +156,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,6 +234,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,6 +276,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -309,6 +314,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: routesResult.redirectRoutes,
|
||||
defaultRoutes: routesResult.defaultRoutes,
|
||||
rewriteRoutes: routesResult.rewriteRoutes,
|
||||
errorRoutes: routesResult.errorRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -898,10 +904,17 @@ function getRouteResult(
|
||||
defaultRoutes: Route[];
|
||||
redirectRoutes: Route[];
|
||||
rewriteRoutes: Route[];
|
||||
errorRoutes: Route[];
|
||||
} {
|
||||
const defaultRoutes: Route[] = [];
|
||||
const redirectRoutes: Route[] = [];
|
||||
const rewriteRoutes: Route[] = [];
|
||||
const errorRoutes: Route[] = [];
|
||||
const isNextjs =
|
||||
frontendBuilder &&
|
||||
((frontendBuilder.use && frontendBuilder.use.startsWith('@vercel/next')) ||
|
||||
(frontendBuilder.config &&
|
||||
frontendBuilder.config.framework === 'nextjs'));
|
||||
|
||||
if (apiRoutes && apiRoutes.length > 0) {
|
||||
if (options.featHandleMiss) {
|
||||
@@ -968,10 +981,21 @@ function getRouteResult(
|
||||
});
|
||||
}
|
||||
|
||||
if (options.featHandleMiss && !isNextjs) {
|
||||
// Exclude Next.js to avoid overriding custom error page
|
||||
// https://nextjs.org/docs/advanced-features/custom-error-page
|
||||
errorRoutes.push({
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: options.cleanUrls ? '/404' : '/404.html',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ export class NowBuildError extends Error {
|
||||
public hideStackTrace = true;
|
||||
public code: string;
|
||||
public link?: string;
|
||||
public action?: string;
|
||||
|
||||
constructor({ message, code, link }: Props) {
|
||||
constructor({ message, code, link, action }: Props) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.link = link;
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +33,8 @@ interface Props {
|
||||
* link to more information about this error.
|
||||
*/
|
||||
link?: string;
|
||||
/**
|
||||
* Optional "action" to display before the `link`, such as "More details".
|
||||
*/
|
||||
action?: string;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
getLatestNodeVersion,
|
||||
getDiscontinuedNodeVersions,
|
||||
} from './fs/node-version';
|
||||
import { NowBuildError } from './errors';
|
||||
import streamToBuffer from './fs/stream-to-buffer';
|
||||
import shouldServe from './should-serve';
|
||||
import debug from './debug';
|
||||
@@ -111,9 +112,11 @@ export const getPlatformEnv = (name: string): string | undefined => {
|
||||
const n = process.env[nName];
|
||||
if (typeof v === 'string') {
|
||||
if (typeof n === 'string') {
|
||||
throw new Error(
|
||||
`Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var`
|
||||
);
|
||||
throw new NowBuildError({
|
||||
code: 'CONFLICTING_ENV_VAR_NAMES',
|
||||
message: `Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var.`,
|
||||
link: 'https://vercel.link/combining-old-and-new-config',
|
||||
});
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -531,6 +531,7 @@ describe('Test `detectBuilders`', () => {
|
||||
const files = ['api/user.php'];
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
});
|
||||
|
||||
@@ -867,12 +868,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
@@ -887,12 +890,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
@@ -913,12 +918,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders).toBe(null);
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('no package.json + public', async () => {
|
||||
@@ -929,6 +937,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(errors).toBe(null);
|
||||
@@ -939,6 +948,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('no package.json + no build + raw static + api', async () => {
|
||||
@@ -949,6 +960,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/users.js');
|
||||
@@ -963,6 +975,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json + no build + root + api', async () => {
|
||||
@@ -990,6 +1004,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, undefined, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/[endpoint]/[id].js');
|
||||
@@ -1002,6 +1017,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(rewriteRoutes!.length).toBe(2);
|
||||
expect((rewriteRoutes![0] as Source).src).toBe('^/api/([^/]+)/([^/]+)$');
|
||||
expect((rewriteRoutes![1] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + next + public', async () => {
|
||||
@@ -1016,6 +1033,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1029,6 +1047,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('api + next + raw static', async () => {
|
||||
@@ -1043,6 +1062,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1056,6 +1076,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('api + raw static', async () => {
|
||||
@@ -1066,6 +1087,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1079,6 +1101,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + raw static + package.json no build script', async () => {
|
||||
@@ -1093,6 +1117,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/version.js');
|
||||
@@ -1106,6 +1131,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + public', async () => {
|
||||
@@ -1116,7 +1143,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
'README.md',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
@@ -1124,6 +1151,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(builders![1].src).toBe('public/**/*');
|
||||
expect(builders!.length).toBe(2);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api go with test files', async () => {
|
||||
@@ -1142,21 +1171,26 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
'api/src/controllers/user.module_test.go',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders!.length).toBe(7);
|
||||
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('just public', async () => {
|
||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].src).toBe('public/**/*');
|
||||
expect(builders![0].use).toBe('@vercel/static');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('next + public', async () => {
|
||||
@@ -1166,10 +1200,13 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'public/index.html', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(0);
|
||||
});
|
||||
|
||||
it('nuxt', async () => {
|
||||
@@ -1179,10 +1216,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/static-build');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('nuxt + tag canary', async () => {
|
||||
@@ -1192,23 +1233,29 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
tag: 'canary',
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/static-build@canary');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json with no build + api', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/[endpoint].js');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json with no build + public directory', async () => {
|
||||
@@ -1327,7 +1374,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
it('many static files + one api file', async () => {
|
||||
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
|
||||
files.push('api/index.ts');
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
@@ -1336,6 +1383,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(builders![0].src).toBe('api/index.ts');
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(builders![1].src).toBe('!{api/**,package.json}');
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('functions with nextjs', async () => {
|
||||
@@ -1530,6 +1579,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
const files = ['api/user.php'];
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1630,6 +1680,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1646,6 +1697,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
// @ts-ignore: Since we test an invalid type
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1681,6 +1733,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1693,6 +1746,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Custom static output directory with api', async () => {
|
||||
@@ -1707,6 +1762,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1722,6 +1778,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Framework with non-package.json entrypoint', async () => {
|
||||
@@ -1730,7 +1788,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
framework: 'hugo',
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, null, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1745,6 +1803,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('No framework, only package.json', async () => {
|
||||
@@ -1755,7 +1815,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
expect(builders).toEqual([
|
||||
{
|
||||
@@ -1766,13 +1828,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Framework with an API', async () => {
|
||||
const files = ['config.rb', 'api/date.rb'];
|
||||
const projectSettings = { framework: 'middleman' };
|
||||
|
||||
const { builders } = await detectBuilders(files, null, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1794,6 +1858,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Error for non-api functions', async () => {
|
||||
@@ -1843,13 +1909,19 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
||||
const files = ['out/index.html'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
const { builders, errors, errorRoutes } = await detectBuilders(
|
||||
files,
|
||||
null,
|
||||
{
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
}
|
||||
);
|
||||
expect(errors).toBe(null);
|
||||
expect(builders![0]!.use).toBe('@vercel/static');
|
||||
expect(builders![0]!.src).toBe('out/**/*');
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('do not require build script when `buildCommand` is an empty string', async () => {
|
||||
@@ -2025,9 +2097,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
||||
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
|
||||
files,
|
||||
null,
|
||||
{
|
||||
featHandleMiss,
|
||||
});
|
||||
}
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -2043,6 +2119,44 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
{
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: '/404.html',
|
||||
},
|
||||
]);
|
||||
|
||||
const pattern = new RegExp(errorRoutes![0].src!);
|
||||
|
||||
[
|
||||
'/',
|
||||
'/index.html',
|
||||
'/page.html',
|
||||
'/page',
|
||||
'/another/index.html',
|
||||
'/another/page.html',
|
||||
'/another/page',
|
||||
'/another/sub/index.html',
|
||||
'/another/sub/page.html',
|
||||
'/another/sub/page',
|
||||
].forEach(file => {
|
||||
expect(file).toMatch(pattern);
|
||||
});
|
||||
|
||||
[
|
||||
'/api',
|
||||
'/api/',
|
||||
'/api/index.html',
|
||||
'/api/page.html',
|
||||
'/api/page',
|
||||
'/api/sub',
|
||||
'/api/sub/index.html',
|
||||
'/api/sub/page.html',
|
||||
'/api/sub/page',
|
||||
].forEach(file => {
|
||||
expect(file).not.toMatch(pattern);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2326,6 +2440,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, options);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
@@ -2336,6 +2451,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
{
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: '/404',
|
||||
},
|
||||
]);
|
||||
|
||||
// expected redirect should match inputs
|
||||
const getLocation = createReplaceLocation(redirectRoutes);
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('Test `getPlatformEnv()`', () => {
|
||||
assert(err);
|
||||
assert.equal(
|
||||
err!.message,
|
||||
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var'
|
||||
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/cgi",
|
||||
"version": "1.0.6-canary.0",
|
||||
"version": "1.0.6",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "19.0.2-canary.8",
|
||||
"version": "19.1.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -62,13 +62,13 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.3.2-canary.2",
|
||||
"@vercel/go": "1.1.2-canary.0",
|
||||
"@vercel/next": "2.6.3-canary.4",
|
||||
"@vercel/node": "1.6.2-canary.4",
|
||||
"@vercel/python": "1.2.2-canary.1",
|
||||
"@vercel/ruby": "1.2.2-canary.0",
|
||||
"@vercel/static-build": "0.17.2-canary.0"
|
||||
"@vercel/build-utils": "2.4.0",
|
||||
"@vercel/go": "1.1.2",
|
||||
"@vercel/next": "2.6.6",
|
||||
"@vercel/node": "1.7.0",
|
||||
"@vercel/python": "1.2.2",
|
||||
"@vercel/ruby": "1.2.2",
|
||||
"@vercel/static-build": "0.17.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
@@ -106,7 +106,7 @@
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.10.2",
|
||||
"ajv": "6.12.2",
|
||||
"alpha-sort": "2.0.1",
|
||||
"ansi-escapes": "3.0.0",
|
||||
"ansi-regex": "3.0.0",
|
||||
@@ -136,6 +136,7 @@
|
||||
"escape-html": "1.0.3",
|
||||
"esm": "3.1.4",
|
||||
"execa": "3.2.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"fs-extra": "7.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "7.1.2",
|
||||
@@ -182,7 +183,7 @@
|
||||
"tmp-promise": "1.0.3",
|
||||
"tree-kill": "1.2.1",
|
||||
"ts-node": "8.3.0",
|
||||
"typescript": "3.6.4",
|
||||
"typescript": "3.9.3",
|
||||
"universal-analytics": "0.4.20",
|
||||
"update-check": "1.5.3",
|
||||
"utility-types": "2.1.0",
|
||||
|
||||
@@ -75,6 +75,17 @@ async function main() {
|
||||
const dest = join(dirRoot, 'dist/runtimes');
|
||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
||||
|
||||
// Band-aid to delete stuff that `ncc` bundles, but it shouldn't:
|
||||
|
||||
// TypeScript definition files from `@vercel/build-utils`
|
||||
await remove(join(dirRoot, 'dist', 'dist'));
|
||||
|
||||
// The Readme and `package.json` from "config-chain" module
|
||||
await remove(join(dirRoot, 'dist', 'config-chain'));
|
||||
|
||||
// A bunch of source `.ts` files from CLI's `util` directory
|
||||
await remove(join(dirRoot, 'dist', 'util'));
|
||||
|
||||
console.log('Finished building `now-cli`');
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||
import {
|
||||
@@ -56,6 +55,7 @@ import validatePaths, {
|
||||
} from '../../util/validate-paths';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
import { getCommandName } from '../../util/pkg-name.ts';
|
||||
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url.ts';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -87,6 +87,7 @@ const addProcessEnv = async (log, env) => {
|
||||
|
||||
const printDeploymentStatus = async (
|
||||
output,
|
||||
client,
|
||||
{
|
||||
readyState,
|
||||
alias: aliasList,
|
||||
@@ -119,18 +120,14 @@ const printDeploymentStatus = async (
|
||||
let previewUrl;
|
||||
let isWildcard;
|
||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
// search for a non now.sh/non wildcard domain
|
||||
// but fallback to the first alias in the list
|
||||
const mainAlias =
|
||||
aliasList.find(
|
||||
alias =>
|
||||
!alias.endsWith('.now.sh') &&
|
||||
!alias.endsWith('.vercel.app') &&
|
||||
!isWildcardAlias(alias)
|
||||
) || aliasList[0];
|
||||
|
||||
isWildcard = isWildcardAlias(mainAlias);
|
||||
previewUrl = isWildcard ? mainAlias : `https://${mainAlias}`;
|
||||
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
|
||||
if (previewUrlInfo) {
|
||||
isWildcard = previewUrlInfo.isWildcard;
|
||||
previewUrl = previewUrlInfo.previewUrl;
|
||||
} else {
|
||||
isWildcard = false;
|
||||
previewUrl = `https://${deploymentUrl}`;
|
||||
}
|
||||
} else {
|
||||
// fallback to deployment url
|
||||
isWildcard = false;
|
||||
@@ -251,7 +248,9 @@ export default async function main(
|
||||
if (argv['--name']) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${param('--name')} flag is deprecated (https://zeit.ink/1B)`,
|
||||
`The ${param(
|
||||
'--name'
|
||||
)} option is deprecated (https://vercel.link/name-flag)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
@@ -389,7 +388,7 @@ export default async function main(
|
||||
`${prependEmoji(
|
||||
`The ${code('name')} property in ${highlight(
|
||||
localConfig[fileNameSymbol]
|
||||
)} is deprecated (https://zeit.ink/5F)`,
|
||||
)} is deprecated (https://vercel.link/name-prop)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
@@ -573,8 +572,11 @@ export default async function main(
|
||||
|
||||
if (deployment instanceof Error) {
|
||||
output.error(
|
||||
`${deployment.message ||
|
||||
'An unexpected error occurred while deploying your project'} (http://zeit.ink/P4)`
|
||||
deployment.message ||
|
||||
'An unexpected error occurred while deploying your project',
|
||||
null,
|
||||
'https://vercel.link/help',
|
||||
'Contact Support'
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -701,6 +703,12 @@ export default async function main(
|
||||
|
||||
return printDeploymentStatus(
|
||||
output,
|
||||
new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
currentTeam: org.type === 'team' ? org.id : null,
|
||||
debug: debugEnabled,
|
||||
}),
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
|
||||
@@ -119,7 +119,7 @@ export default async function main(ctx: NowContext) {
|
||||
try {
|
||||
return await dev(ctx, argv, args, output);
|
||||
} catch (err) {
|
||||
output.error(err.message);
|
||||
output.prettyError(err);
|
||||
output.debug(stringifyError(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ const login = async ctx => {
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://zeit.ink/1X).`,
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git).`,
|
||||
emoji('tip')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import chalk from 'chalk';
|
||||
import table from 'text-table';
|
||||
import mri from 'mri';
|
||||
import ms from 'ms';
|
||||
import strlen from '../util/strlen';
|
||||
import getArgs from '../util/get-args';
|
||||
import { handleError, error } from '../util/error';
|
||||
import exit from '../util/exit';
|
||||
import Client from '../util/client.ts';
|
||||
@@ -11,7 +11,6 @@ import getScope from '../util/get-scope';
|
||||
import createOutput from '../util/output';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
import wait from '../util/output/wait';
|
||||
import getPrefixedFlags from '../util/get-prefixed-flags';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
||||
|
||||
const e = encodeURIComponent;
|
||||
@@ -56,23 +55,25 @@ let apiUrl;
|
||||
let subcommand;
|
||||
|
||||
const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
next: 'N',
|
||||
},
|
||||
try {
|
||||
argv = getArgs(ctx.argv.slice(2), {
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
debug = argv.debug;
|
||||
debug = argv['--debug'];
|
||||
apiUrl = ctx.apiUrl;
|
||||
subcommand = argv._[0];
|
||||
subcommand = argv._[0] || 'list';
|
||||
|
||||
if (argv.help || !subcommand) {
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
await exit(0);
|
||||
return exit(2);
|
||||
}
|
||||
|
||||
const output = createOutput({ debug });
|
||||
@@ -126,15 +127,16 @@ async function run({ client, contextName }) {
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
return exit(2);
|
||||
}
|
||||
|
||||
const stopSpinner = wait(`Fetching projects in ${chalk.bold(contextName)}`);
|
||||
|
||||
let projectsUrl = '/v4/projects/?limit=20';
|
||||
|
||||
if (argv.next) {
|
||||
projectsUrl += `&until=${argv.next}`;
|
||||
const next = argv['--next'];
|
||||
if (next) {
|
||||
projectsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
const { projects: list, pagination } = await client.fetch(projectsUrl, {
|
||||
@@ -175,14 +177,7 @@ async function run({ client, contextName }) {
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const prefixedArgs = getPrefixedFlags(argv);
|
||||
const flags = getCommandFlags(prefixedArgs, [
|
||||
'_',
|
||||
'--next',
|
||||
'-N',
|
||||
'-d',
|
||||
'-y',
|
||||
]);
|
||||
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||
const nextCmd = `projects ls${flags} --next ${pagination.next}`;
|
||||
console.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
||||
}
|
||||
@@ -271,7 +266,7 @@ async function run({ client, contextName }) {
|
||||
|
||||
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||
help();
|
||||
exit(1);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import checkForUpdate from 'update-check';
|
||||
import ms from 'ms';
|
||||
import { URL } from 'url';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import getGlobalPathConfig from './util/config/global-path';
|
||||
import {
|
||||
getDefaultConfig,
|
||||
@@ -114,10 +115,10 @@ const main = async argv_ => {
|
||||
}
|
||||
|
||||
if (
|
||||
localConfig instanceof NowError &&
|
||||
(localConfig instanceof NowError || localConfig instanceof NowBuildError) &&
|
||||
!(localConfig instanceof ERRORS.CantFindConfig)
|
||||
) {
|
||||
output.error(`Failed to load local config file: ${localConfig.message}`);
|
||||
output.prettyError(localConfig);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -529,7 +530,7 @@ const main = async argv_ => {
|
||||
|
||||
if (argv['--team']) {
|
||||
output.warn(
|
||||
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||
`The ${param('--team')} option is deprecated. Please use ${param(
|
||||
'--scope'
|
||||
)} instead.`
|
||||
);
|
||||
@@ -644,11 +645,15 @@ const main = async argv_ => {
|
||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||
);
|
||||
output.debug(err.stack);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
await reportError(Sentry, err, apiUrl, configFiles);
|
||||
if (shouldCollectMetrics) {
|
||||
metric
|
||||
.event(eventCategory, '1', pkg.version)
|
||||
.exception(err.message)
|
||||
.send();
|
||||
}
|
||||
|
||||
// If there is a code we should not consider the error unexpected
|
||||
// but instead show the message. Any error that is handled by this should
|
||||
@@ -656,28 +661,17 @@ const main = async argv_ => {
|
||||
// that happens for anything that lands here. It should NOT bubble up to here.
|
||||
if (err.code) {
|
||||
output.debug(err.stack);
|
||||
output.error(err.message);
|
||||
|
||||
if (shouldCollectMetrics) {
|
||||
metric
|
||||
.event(eventCategory, '1', pkg.version)
|
||||
.exception(err.message)
|
||||
.send();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (shouldCollectMetrics) {
|
||||
metric
|
||||
.event(eventCategory, '1', pkg.version)
|
||||
.exception(err.message)
|
||||
.send();
|
||||
}
|
||||
output.prettyError(err);
|
||||
} else {
|
||||
await reportError(Sentry, err, apiUrl, configFiles);
|
||||
|
||||
// Otherwise it is an unexpected error and we should show the trace
|
||||
// and an unexpected error message
|
||||
output.error(`An unexpected error occurred in ${subcommand}: ${err.stack}`);
|
||||
output.error(
|
||||
`An unexpected error occurred in ${subcommand}: ${err.stack}`
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -688,8 +682,6 @@ const main = async argv_ => {
|
||||
return exitCode;
|
||||
};
|
||||
|
||||
debug('start');
|
||||
|
||||
const handleRejection = async err => {
|
||||
debug('handling rejection');
|
||||
|
||||
|
||||
17
packages/now-cli/src/util/certs/get-certs-for-cn.ts
Normal file
17
packages/now-cli/src/util/certs/get-certs-for-cn.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { stringify } from 'querystring';
|
||||
import { Cert } from '../../types';
|
||||
import Client from '../client';
|
||||
|
||||
/**
|
||||
* Returns certs that contain @param cn.
|
||||
*/
|
||||
export async function getCertsForCn(
|
||||
client: Client,
|
||||
cn: string,
|
||||
{ limit }: { limit?: number } = {}
|
||||
) {
|
||||
const { certs } = await client.fetch<{
|
||||
certs: Cert[];
|
||||
}>(`/v4/now/certs?${stringify({ cn, ...(limit ? { limit } : {}) })}`);
|
||||
return certs;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export default class Client extends EventEmitter {
|
||||
_withCache: boolean;
|
||||
_output: Output;
|
||||
_token: string;
|
||||
currentTeam?: string;
|
||||
currentTeam?: string | null;
|
||||
|
||||
constructor({
|
||||
apiUrl,
|
||||
@@ -36,7 +36,7 @@ export default class Client extends EventEmitter {
|
||||
}: {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
currentTeam?: string;
|
||||
currentTeam?: string | null;
|
||||
forceNew?: boolean;
|
||||
withCache?: boolean;
|
||||
debug?: boolean;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
import mri from 'mri';
|
||||
import { InvalidLocalConfig } from '../errors';
|
||||
import { ConflictingConfigFiles } from '../errors-ts';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export default function getLocalPathConfig(prefix: string) {
|
||||
@@ -11,20 +12,30 @@ export default function getLocalPathConfig(prefix: string) {
|
||||
},
|
||||
});
|
||||
|
||||
// If `--local-config` flag was specified, then that takes priority
|
||||
const customPath = args['local-config'];
|
||||
|
||||
if (customPath && typeof customPath !== 'string') {
|
||||
if (customPath) {
|
||||
if (typeof customPath !== 'string') {
|
||||
throw new InvalidLocalConfig(customPath);
|
||||
}
|
||||
return path.resolve(prefix, customPath);
|
||||
}
|
||||
|
||||
const possibleConfigFiles = [
|
||||
path.join(prefix, 'vercel.json'),
|
||||
path.join(prefix, 'now.json'),
|
||||
];
|
||||
// Otherwise check for either `vercel.json` or `now.json`.
|
||||
// Throw an error if both exist.
|
||||
const vercelConfigPath = path.join(prefix, 'vercel.json');
|
||||
const nowConfigPath = path.join(prefix, 'now.json');
|
||||
|
||||
return (
|
||||
(customPath && path.resolve(prefix, customPath)) ||
|
||||
possibleConfigFiles.find(configFile => existsSync(configFile)) ||
|
||||
possibleConfigFiles[0]
|
||||
);
|
||||
const vercelConfigExists = existsSync(vercelConfigPath);
|
||||
const nowConfigExists = existsSync(nowConfigPath);
|
||||
|
||||
if (nowConfigExists && vercelConfigExists) {
|
||||
throw new ConflictingConfigFiles([vercelConfigPath, nowConfigPath]);
|
||||
}
|
||||
|
||||
if (nowConfigExists) {
|
||||
return nowConfigPath;
|
||||
}
|
||||
|
||||
return vercelConfigPath;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import isWildcardAlias from '../alias/is-wildcard-alias';
|
||||
import { getCertsForCn } from '../certs/get-certs-for-cn';
|
||||
import Client from '../client';
|
||||
|
||||
/**
|
||||
* Tries to find the "best" alias url.
|
||||
* @param aliasList
|
||||
*/
|
||||
export async function getPreferredPreviewURL(
|
||||
client: Client,
|
||||
aliasList: string[]
|
||||
) {
|
||||
if (aliasList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks for non public aliases and non wildcard domains.
|
||||
*/
|
||||
const preferredAliases = aliasList.filter(
|
||||
alias =>
|
||||
!alias.endsWith('.now.sh') &&
|
||||
!alias.endsWith('.vercel.app') &&
|
||||
!isWildcardAlias(alias)
|
||||
);
|
||||
for (const alias of preferredAliases) {
|
||||
const certs = await getCertsForCn(client, alias, { limit: 1 }).catch(() => {
|
||||
return null;
|
||||
});
|
||||
if (certs && certs.length > 0) {
|
||||
return { previewUrl: `https://${alias}`, isWildcard: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback to first alias
|
||||
*/
|
||||
const [firstAlias] = aliasList;
|
||||
if (isWildcardAlias(firstAlias)) {
|
||||
return { previewUrl: firstAlias, isWildcard: true };
|
||||
}
|
||||
|
||||
if (firstAlias.endsWith('.vercel.app') || firstAlias.endsWith('.now.sh')) {
|
||||
return { previewUrl: `https://${firstAlias}`, isWildcard: false };
|
||||
}
|
||||
|
||||
return { previewUrl: `http://${firstAlias}`, isWildcard: false };
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import semver from 'semver';
|
||||
import npa from 'npm-package-arg';
|
||||
import pluralize from 'pluralize';
|
||||
import { basename, join } from 'path';
|
||||
import { PackageJson } from '@vercel/build-utils';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
||||
import { NowBuildError, PackageJson } from '@vercel/build-utils';
|
||||
import cliPkg from '../pkg';
|
||||
|
||||
import { NoBuilderCacheError } from '../errors-ts';
|
||||
@@ -232,21 +232,41 @@ async function npmInstall(
|
||||
output.debug(`Running npm install in ${cwd}`);
|
||||
|
||||
try {
|
||||
await execa(
|
||||
'npm',
|
||||
[
|
||||
const args = [
|
||||
'install',
|
||||
'--save-exact',
|
||||
'--no-package-lock',
|
||||
'--no-audit',
|
||||
'--no-progress',
|
||||
...sortedPackages,
|
||||
],
|
||||
{
|
||||
cwd,
|
||||
stdio: output.isDebugEnabled() ? 'inherit' : undefined,
|
||||
];
|
||||
if (process.stderr.isTTY) {
|
||||
// Force colors in the npm child process
|
||||
// https://docs.npmjs.com/misc/config#color
|
||||
args.push('--color=always');
|
||||
}
|
||||
args.push(...sortedPackages);
|
||||
const result = await execa('npm', args, {
|
||||
cwd,
|
||||
reject: false,
|
||||
stdio: output.isDebugEnabled() ? 'inherit' : 'pipe',
|
||||
});
|
||||
if (result.failed) {
|
||||
stopSpinner();
|
||||
if (result.stdout) {
|
||||
console.log(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
console.error(result.stderr);
|
||||
}
|
||||
throw new NowBuildError({
|
||||
message:
|
||||
(result as any).code === 'ENOENT'
|
||||
? '`npm` is not installed'
|
||||
: 'Failed to install `vercel dev` dependencies',
|
||||
code: 'NPM_INSTALL_ERROR',
|
||||
link: 'https://vercel.link/npm-install-failed-dev',
|
||||
});
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
stopSpinner();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import which from 'which';
|
||||
|
||||
import { getVercelIgnore, fileNameSymbol } from '@vercel/client';
|
||||
@@ -114,6 +115,7 @@ export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
public output: Output;
|
||||
public proxy: httpProxy;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug: string | null;
|
||||
public files: BuilderInputs;
|
||||
@@ -124,7 +126,6 @@ export default class DevServer {
|
||||
private caseSensitive: boolean;
|
||||
private apiDir: string | null;
|
||||
private apiExtensions: Set<string>;
|
||||
private proxy: httpProxy;
|
||||
private server: http.Server;
|
||||
private stopping: boolean;
|
||||
private buildMatches: Map<string, BuildMatch>;
|
||||
@@ -234,8 +235,18 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
|
||||
// Update the env vars configuration
|
||||
const nowConfigBuild = nowConfig.build || {};
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
|
||||
const filesChangedArray = [...filesChanged];
|
||||
@@ -400,7 +411,7 @@ export default class DevServer {
|
||||
const blockingBuilds: Promise<void>[] = [];
|
||||
for (const match of matches) {
|
||||
const currentMatch = this.buildMatches.get(match.src);
|
||||
if (!currentMatch || currentMatch.use !== match.use) {
|
||||
if (!buildMatchEquals(currentMatch, match)) {
|
||||
this.output.debug(
|
||||
`Adding build match for "${match.src}" with "${match.use}"`
|
||||
);
|
||||
@@ -522,18 +533,11 @@ export default class DevServer {
|
||||
// The default empty `vercel.json` is used to serve all files as static
|
||||
// when no `vercel.json` is present
|
||||
let configPath = 'vercel.json';
|
||||
let config: NowConfig = this.cachedNowConfig || {
|
||||
let config: NowConfig = {
|
||||
version: 2,
|
||||
[fileNameSymbol]: configPath,
|
||||
};
|
||||
|
||||
// We need to delete these properties for zero config to work
|
||||
// with file changes
|
||||
if (this.cachedNowConfig) {
|
||||
delete this.cachedNowConfig.builds;
|
||||
delete this.cachedNowConfig.routes;
|
||||
}
|
||||
|
||||
try {
|
||||
configPath = getNowConfigPath(this.cwd);
|
||||
this.output.debug(`Reading ${configPath}`);
|
||||
@@ -564,7 +568,7 @@ export default class DevServer {
|
||||
nowConfig: config,
|
||||
});
|
||||
if (routeError) {
|
||||
this.output.error(routeError.message);
|
||||
this.output.prettyError(routeError);
|
||||
await this.exit();
|
||||
}
|
||||
config.routes = maybeRoutes || [];
|
||||
@@ -581,6 +585,7 @@ export default class DevServer {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
@@ -606,8 +611,9 @@ export default class DevServer {
|
||||
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
}
|
||||
|
||||
const routes: Route[] = [];
|
||||
let routes: Route[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(
|
||||
@@ -617,10 +623,14 @@ export default class DevServer {
|
||||
phase: 'filesystem',
|
||||
})
|
||||
);
|
||||
routes = appendRoutesToPhase({
|
||||
routes,
|
||||
newRoutes: errorRoutes,
|
||||
phase: 'error',
|
||||
});
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(config.builds)) {
|
||||
if (this.devCommand) {
|
||||
@@ -767,7 +777,6 @@ export default class DevServer {
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
Object.assign(process.env, allEnv);
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
@@ -1323,8 +1332,7 @@ export default class DevServer {
|
||||
const handleMap = getRoutesTypes(routes);
|
||||
const missRoutes = handleMap.get('miss') || [];
|
||||
const hitRoutes = handleMap.get('hit') || [];
|
||||
handleMap.delete('miss');
|
||||
handleMap.delete('hit');
|
||||
const errorRoutes = handleMap.get('error') || [];
|
||||
const phases: (HandleValue | null)[] = [null, 'filesystem'];
|
||||
|
||||
let routeResult: RouteResult | null = null;
|
||||
@@ -1362,7 +1370,7 @@ export default class DevServer {
|
||||
|
||||
debug(`ProxyPass: ${destUrl}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(req, res, destUrl, this.proxy, this.output);
|
||||
return proxyPass(req, res, destUrl, this, nowRequestId);
|
||||
}
|
||||
|
||||
match = await findBuildMatch(
|
||||
@@ -1431,6 +1439,32 @@ export default class DevServer {
|
||||
routeResult.status = prevStatus;
|
||||
}
|
||||
|
||||
if (!match && errorRoutes.length > 0) {
|
||||
// error phase
|
||||
const routeResultForError = await devRouter(
|
||||
getReqUrl(routeResult),
|
||||
req.method,
|
||||
errorRoutes,
|
||||
this,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'error'
|
||||
);
|
||||
|
||||
const matchForError = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResultForError.dest,
|
||||
this
|
||||
);
|
||||
|
||||
if (matchForError) {
|
||||
// error phase only applies if the file was found
|
||||
routeResult = routeResultForError;
|
||||
match = matchForError;
|
||||
}
|
||||
}
|
||||
|
||||
statusCode = routeResult.status;
|
||||
|
||||
if (match) {
|
||||
@@ -1475,8 +1509,8 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
}
|
||||
@@ -1560,7 +1594,7 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_STATUS_CODE_FROM_DEV_SERVER',
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
return;
|
||||
@@ -1595,15 +1629,14 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${port}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
debug(`Skipping \`startDevServer()\` for ${match.entrypoint}`);
|
||||
}
|
||||
}
|
||||
|
||||
let foundAsset = findAsset(match, requestPath, nowConfig);
|
||||
|
||||
if (!foundAsset && callLevel === 0) {
|
||||
@@ -1626,8 +1659,8 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
}
|
||||
@@ -1721,7 +1754,7 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_STATUS_CODE_FROM_LAMBDA',
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
return;
|
||||
@@ -1937,24 +1970,27 @@ function proxyPass(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
dest: string,
|
||||
proxy: httpProxy,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
nowRequestId: string,
|
||||
ignorePath: boolean = true
|
||||
): void {
|
||||
return proxy.web(
|
||||
return devServer.proxy.web(
|
||||
req,
|
||||
res,
|
||||
{ target: dest, ignorePath },
|
||||
(error: NodeJS.ErrnoException) => {
|
||||
// If the client hangs up a socket, we do not
|
||||
// want to do anything, as the client just expects
|
||||
// the connection to be closed.
|
||||
if (error.code === 'ECONNRESET') {
|
||||
res.end();
|
||||
return;
|
||||
devServer.output.error(
|
||||
`Failed to complete request to ${req.url}: ${error}`
|
||||
);
|
||||
if (!res.headersSent) {
|
||||
devServer.sendError(
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
}
|
||||
|
||||
output.error(`Failed to complete request to ${req.url}: ${error}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2014,12 +2050,24 @@ async function findBuildMatch(
|
||||
isFilesystem?: boolean
|
||||
): Promise<BuildMatch | null> {
|
||||
requestPath = requestPath.replace(/^\//, '');
|
||||
|
||||
let bestIndexMatch: undefined | BuildMatch;
|
||||
for (const match of matches.values()) {
|
||||
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) {
|
||||
if (!isIndex(match.src)) {
|
||||
return match;
|
||||
} else {
|
||||
// if isIndex === true and ends in .html, we're done. Otherwise, keep searching
|
||||
bestIndexMatch = match;
|
||||
if (extname(match.src) === '.html') {
|
||||
return bestIndexMatch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// return a non-.html index file or none are found
|
||||
return bestIndexMatch || null;
|
||||
}
|
||||
|
||||
async function shouldServe(
|
||||
@@ -2216,3 +2264,11 @@ function hasNewRoutingProperties(nowConfig: NowConfig) {
|
||||
typeof nowConfig.trailingSlash !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
function buildMatchEquals(a?: BuildMatch, b?: BuildMatch): boolean {
|
||||
if (!a || !b) return false;
|
||||
if (a.src !== b.src) return false;
|
||||
if (a.use !== b.use) return false;
|
||||
if (!deepEqual(a.config || {}, b.config || {})) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,22 +44,16 @@
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
If you are a visitor; contact the website owner or try again later.
|
||||
</li>
|
||||
<li>
|
||||
If you are the owner; <a href="/_logs" target="_blank">check the logs</a> for the application error.
|
||||
Check the logs in your terminal window to see the application error.
|
||||
</li>
|
||||
</ul>
|
||||
<a target="_blank" href="https://vercel.com/docs/router-status/{{= it.http_status_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
||||
<a target="_blank" href="https://vercel.com/docs/error/application/{{= it.error_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
||||
</p>
|
||||
{{??}}
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
If you are a visitor, please try again later.
|
||||
</li>
|
||||
<li>
|
||||
If you are the owner, no action is needed. Our engineers have been notified.
|
||||
Please open a <a target="_blank" href="https://github.com/vercel/vercel/issues/new/choose">GitHub issue</a> describing the problem you are experiencing with <code>vercel dev</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bytes from 'bytes';
|
||||
import { Response } from 'node-fetch';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import { NowError } from './now-error';
|
||||
import code from './output/code';
|
||||
import { getCommandName } from './pkg-name';
|
||||
@@ -771,6 +772,20 @@ export class CantParseJSONFile extends NowError<
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingConfigFiles extends NowBuildError {
|
||||
files: string[];
|
||||
|
||||
constructor(files: string[]) {
|
||||
super({
|
||||
code: 'CONFLICTING_CONFIG_FILES',
|
||||
message:
|
||||
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.',
|
||||
link: 'https://vercel.link/combining-old-and-new-config',
|
||||
});
|
||||
this.files = files;
|
||||
}
|
||||
}
|
||||
|
||||
export class CantFindConfig extends NowError<
|
||||
'CANT_FIND_CONFIG',
|
||||
{ paths: string[] }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { fileNameSymbol } from '@vercel/client';
|
||||
import {
|
||||
CantParseJSONFile,
|
||||
CantFindConfig,
|
||||
ConflictingConfigFiles,
|
||||
WorkingDirectoryDoesNotExist,
|
||||
} from './errors-ts';
|
||||
import humanizePath from './humanize-path';
|
||||
@@ -49,28 +50,31 @@ export default async function getConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// Then try with vercel.json in the same directory
|
||||
// Then try with `vercel.json` or `now.json` in the same directory
|
||||
const vercelFilePath = path.resolve(localPath, 'vercel.json');
|
||||
const vercelConfig = await readJSONFile(vercelFilePath);
|
||||
const nowFilePath = path.resolve(localPath, 'now.json');
|
||||
const [vercelConfig, nowConfig] = await Promise.all([
|
||||
readJSONFile(vercelFilePath),
|
||||
readJSONFile(nowFilePath),
|
||||
]);
|
||||
if (vercelConfig instanceof CantParseJSONFile) {
|
||||
return vercelConfig;
|
||||
}
|
||||
if (nowConfig instanceof CantParseJSONFile) {
|
||||
return nowConfig;
|
||||
}
|
||||
if (vercelConfig && nowConfig) {
|
||||
return new ConflictingConfigFiles([vercelFilePath, nowFilePath]);
|
||||
}
|
||||
if (vercelConfig !== null) {
|
||||
output.debug(`Found config in file ${vercelFilePath}`);
|
||||
output.debug(`Found config in file "${vercelFilePath}"`);
|
||||
config = vercelConfig as NowConfig;
|
||||
config[fileNameSymbol] = 'vercel.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
// Then try with now.json in the same directory
|
||||
const nowFilePath = path.resolve(localPath, 'now.json');
|
||||
const mainConfig = await readJSONFile(nowFilePath);
|
||||
if (mainConfig instanceof CantParseJSONFile) {
|
||||
return mainConfig;
|
||||
}
|
||||
if (mainConfig !== null) {
|
||||
output.debug(`Found config in file ${nowFilePath}`);
|
||||
config = mainConfig as NowConfig;
|
||||
if (nowConfig !== null) {
|
||||
output.debug(`Found config in file "${nowFilePath}"`);
|
||||
config = nowConfig as NowConfig;
|
||||
config[fileNameSymbol] = 'now.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { format } from 'util';
|
||||
import { Console } from 'console';
|
||||
import renderLink from './link';
|
||||
import wait from './wait';
|
||||
|
||||
export type Output = ReturnType<typeof createOutput>;
|
||||
@@ -41,7 +42,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
boxen(
|
||||
chalk.bold.yellow('WARN! ') +
|
||||
str +
|
||||
(details ? `\nMore details: ${details}` : ''),
|
||||
(details ? `\nMore details: ${renderLink(details)}` : ''),
|
||||
{
|
||||
padding: {
|
||||
top: 0,
|
||||
@@ -64,16 +65,21 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
|
||||
function error(
|
||||
str: string,
|
||||
slug: string | null = null,
|
||||
link: string | null = null
|
||||
slug?: string,
|
||||
link?: string,
|
||||
action = 'More details'
|
||||
) {
|
||||
print(`${chalk.red(`Error!`)} ${str}\n`);
|
||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||
if (details) {
|
||||
print(`More details: ${details}\n`);
|
||||
print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
function prettyError(err: Error & { link?: string; action?: string }) {
|
||||
return error(err.message, undefined, err.link, err.action);
|
||||
}
|
||||
|
||||
function ready(str: string) {
|
||||
print(`${chalk.cyan('> Ready!')} ${str}\n`);
|
||||
}
|
||||
@@ -106,8 +112,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
return wait(message, delay);
|
||||
}
|
||||
|
||||
// This is pretty hacky, but since we control the version of Node.js
|
||||
// being used because of `pkg` it's safe to do in this case.
|
||||
const c = {
|
||||
_times: new Map(),
|
||||
log(a: string, ...args: string[]) {
|
||||
@@ -134,6 +138,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
prettyError,
|
||||
ready,
|
||||
success,
|
||||
debug,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import { metrics, shouldCollectMetrics } from '../metrics';
|
||||
import { APIError } from '../errors-ts';
|
||||
import renderLink from './link';
|
||||
|
||||
const metric = metrics();
|
||||
|
||||
@@ -9,10 +10,9 @@ export default function error(...input: string[] | [APIError]) {
|
||||
if (typeof input[0] === 'object') {
|
||||
const { slug, message, link } = input[0];
|
||||
messages = [message];
|
||||
if (slug) {
|
||||
messages.push(`> More details: https://err.sh/now/${slug}`);
|
||||
} else if (link) {
|
||||
messages.push(`> More details: ${link}`);
|
||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||
if (details) {
|
||||
messages.push(`${chalk.bold('More details')}: ${renderLink(details)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import chalk from 'chalk';
|
||||
import { prependEmoji, emoji } from '../emoji';
|
||||
import AJV from 'ajv';
|
||||
import { isDirectory } from '../config/global-path';
|
||||
import { getPlatformEnv } from '@vercel/build-utils';
|
||||
import { NowBuildError, getPlatformEnv } from '@vercel/build-utils';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
@@ -42,10 +42,21 @@ const linkSchema = {
|
||||
/**
|
||||
* Returns the `<cwd>/.vercel` directory for the current project
|
||||
* with a fallback to <cwd>/.now` if it exists.
|
||||
*
|
||||
* Throws an error if *both* `.vercel` and `.now` directories exist.
|
||||
*/
|
||||
export function getVercelDirectory(cwd: string = process.cwd()) {
|
||||
export function getVercelDirectory(cwd: string = process.cwd()): string {
|
||||
const possibleDirs = [join(cwd, VERCEL_DIR), join(cwd, VERCEL_DIR_FALLBACK)];
|
||||
return possibleDirs.find(d => isDirectory(d)) || possibleDirs[0];
|
||||
const existingDirs = possibleDirs.filter(d => isDirectory(d));
|
||||
if (existingDirs.length > 1) {
|
||||
throw new NowBuildError({
|
||||
code: 'CONFLICTING_CONFIG_DIRECTORIES',
|
||||
message:
|
||||
'Both `.vercel` and `.now` directories exist. Please remove the `.now` directory.',
|
||||
link: 'https://vercel.link/combining-old-and-new-config',
|
||||
});
|
||||
}
|
||||
return existingDirs[0] || possibleDirs[0];
|
||||
}
|
||||
|
||||
async function getLink(path?: string): Promise<ProjectLink | null> {
|
||||
|
||||
@@ -82,7 +82,7 @@ export default async function validatePaths(
|
||||
if (isFile) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
'Deploying files with Vercel is deprecated (https://zeit.ink/3Z)',
|
||||
'Deploying files with Vercel is deprecated (https://vercel.link/faq-deploy-file)',
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/00-list-directory/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/00-list-directory/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function Custom404() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Custom Gatsby 404</h1>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default Custom404;
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function Custom404() {
|
||||
return <h1>Custom Next 404</h1>;
|
||||
}
|
||||
1
packages/now-cli/test/dev/fixtures/force-module-commonjs/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/force-module-commonjs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function(req, res) {
|
||||
res.end('Force "module: commonjs" JavaScript with ES Modules API endpoint');
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
|
||||
export default function(req: IncomingMessage, res: ServerResponse) {
|
||||
res.end('Force "module: commonjs" TypeScript API endpoint');
|
||||
}
|
||||
2
packages/now-cli/test/dev/fixtures/force-module-commonjs/next-env.d.ts
vendored
Normal file
2
packages/now-cli/test/dev/fixtures/force-module-commonjs/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "force-module-commonjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^9.3.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.0.9",
|
||||
"@types/react": "^16.9.32",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function () {
|
||||
return <div>Force "module: commonjs" test page</div>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"paths": {
|
||||
"@components/*": ["components/*"],
|
||||
"@lib/*": ["lib/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "api"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
5633
packages/now-cli/test/dev/fixtures/force-module-commonjs/yarn.lock
Normal file
5633
packages/now-cli/test/dev/fixtures/force-module-commonjs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
This is index.css
|
||||
@@ -0,0 +1,2 @@
|
||||
This is index.html
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default (req, res) => {
|
||||
res.send(process.env);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
{ "env": { "FOO": "bar" } }
|
||||
1
packages/now-cli/test/dev/fixtures/nested-tsconfig/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/nested-tsconfig/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1,5 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
|
||||
export default function(req: IncomingMessage, res: ServerResponse) {
|
||||
res.end('Nested `tsconfig.json` API endpoint');
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "api",
|
||||
"devDependencies": {
|
||||
"@types/node": "12"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"strict": true,
|
||||
"module": "CommonJS",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/node@12":
|
||||
version "12.12.43"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.43.tgz#b60ce047822e526e7a9252e50844eee79d5386ff"
|
||||
integrity sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA==
|
||||
2
packages/now-cli/test/dev/fixtures/nested-tsconfig/next-env.d.ts
vendored
Normal file
2
packages/now-cli/test/dev/fixtures/nested-tsconfig/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "nested-tsconfig",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^9.3.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.0.9",
|
||||
"@types/react": "^16.9.32",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function () {
|
||||
return <div>Nested tsconfig.json test page</div>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"paths": {
|
||||
"@components/*": ["components/*"],
|
||||
"@lib/*": ["lib/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "api"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
5633
packages/now-cli/test/dev/fixtures/nested-tsconfig/yarn.lock
Normal file
5633
packages/now-cli/test/dev/fixtures/nested-tsconfig/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
10
packages/now-cli/test/dev/fixtures/node-helpers/index.js
Normal file
10
packages/now-cli/test/dev/fixtures/node-helpers/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export default (req, res) => {
|
||||
const hasHelpers = typeof req.query !== 'undefined';
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
hasHelpers,
|
||||
query: req.query,
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
{ "builds": [{ "src": "index.js", "use": "@vercel/node@canary" }] }
|
||||
1
packages/now-cli/test/dev/fixtures/public-and-api/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/public-and-api/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!public
|
||||
@@ -0,0 +1 @@
|
||||
Custom 404 Page
|
||||
1
packages/now-cli/test/dev/fixtures/routes-check-true-status/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/routes-check-true-status/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (_req, res) => {
|
||||
res.end('Hello');
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
Exact Custom 404
|
||||
@@ -0,0 +1 @@
|
||||
Home Page
|
||||
@@ -0,0 +1 @@
|
||||
Custom User 404
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"routes": [
|
||||
{ "src": "/exact", "status": 404, "dest": "/exact-404.html" },
|
||||
{ "handle": "filesystem" },
|
||||
{ "src": "/(.*)", "status": 404, "dest": "/user-404.html" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"builds": [{ "src": "*.js", "use": "@vercel/does-not-exist" }]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Custom 404 Page
|
||||
@@ -0,0 +1 @@
|
||||
The about page
|
||||
@@ -0,0 +1 @@
|
||||
Contact Me Subdirectory
|
||||
@@ -0,0 +1 @@
|
||||
This is the home page
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cleanUrls": true
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Custom 404 Page
|
||||
@@ -0,0 +1 @@
|
||||
The about page
|
||||
@@ -0,0 +1 @@
|
||||
Contact Subdirectory
|
||||
@@ -0,0 +1 @@
|
||||
This is the home page
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 2,
|
||||
"trailingSlash": true
|
||||
}
|
||||
@@ -161,7 +161,7 @@ async function testPath(
|
||||
Object.entries(headers).forEach(([key, expectedValue]) => {
|
||||
let actualValue = res.headers.get(key);
|
||||
if (key.toLowerCase() === 'location' && actualValue === '//') {
|
||||
// HACK: `node-fetch` has strang behavior for location header so fix it
|
||||
// HACK: `node-fetch` has strange behavior for location header so fix it
|
||||
// with `manual-dont-change` opt and convert double slash to single.
|
||||
// See https://github.com/node-fetch/node-fetch/issues/417#issuecomment-587233352
|
||||
actualValue = '/';
|
||||
@@ -187,20 +187,29 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
}
|
||||
);
|
||||
|
||||
const stdoutList = [];
|
||||
const stderrList = [];
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
const readyResolver = createResolver();
|
||||
const exitResolver = createResolver();
|
||||
|
||||
dev.stderr.on('data', data => stderrList.push(Buffer.from(data)));
|
||||
dev.stdout.on('data', data => stdoutList.push(Buffer.from(data)));
|
||||
dev.stdout.setEncoding('utf8');
|
||||
dev.stderr.setEncoding('utf8');
|
||||
|
||||
dev.stdout.on('data', data => {
|
||||
stdout += data;
|
||||
});
|
||||
dev.stderr.on('data', data => {
|
||||
stderr += data;
|
||||
|
||||
if (stderr.includes('Ready! Available at')) {
|
||||
readyResolver.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
let printedOutput = false;
|
||||
|
||||
dev.on('exit', () => {
|
||||
if (!printedOutput) {
|
||||
const stdout = Buffer.concat(stdoutList).toString();
|
||||
const stderr = Buffer.concat(stderrList).toString();
|
||||
printOutput(directory, stdout, stderr);
|
||||
printedOutput = true;
|
||||
}
|
||||
@@ -209,8 +218,6 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
|
||||
dev.on('error', () => {
|
||||
if (!printedOutput) {
|
||||
const stdout = Buffer.concat(stdoutList).toString();
|
||||
const stderr = Buffer.concat(stderrList).toString();
|
||||
printOutput(directory, stdout, stderr);
|
||||
printedOutput = true;
|
||||
}
|
||||
@@ -226,6 +233,7 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
return {
|
||||
dev,
|
||||
port,
|
||||
readyResolver,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -267,14 +275,12 @@ function testFixtureStdio(
|
||||
|
||||
await runNpmInstall(cwd);
|
||||
|
||||
const stdoutList = [];
|
||||
const stderrList = [];
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
const readyResolver = createResolver();
|
||||
const exitResolver = createResolver();
|
||||
|
||||
try {
|
||||
let stderr = '';
|
||||
let printedOutput = false;
|
||||
|
||||
const env = skipDeploy
|
||||
@@ -285,17 +291,19 @@ function testFixtureStdio(
|
||||
env,
|
||||
});
|
||||
|
||||
dev.stdout.setEncoding('utf8');
|
||||
dev.stderr.setEncoding('utf8');
|
||||
|
||||
dev.stdout.pipe(process.stdout);
|
||||
dev.stderr.pipe(process.stderr);
|
||||
|
||||
dev.stdout.on('data', data => {
|
||||
stdoutList.push(data);
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
dev.stderr.on('data', data => {
|
||||
stderrList.push(data);
|
||||
stderr += data;
|
||||
|
||||
stderr += data.toString();
|
||||
if (stderr.includes('Ready! Available at')) {
|
||||
readyResolver.resolve();
|
||||
}
|
||||
@@ -315,8 +323,6 @@ function testFixtureStdio(
|
||||
|
||||
dev.on('exit', () => {
|
||||
if (!printedOutput) {
|
||||
const stdout = Buffer.concat(stdoutList).toString();
|
||||
const stderr = Buffer.concat(stderrList).toString();
|
||||
printOutput(directory, stdout, stderr);
|
||||
printedOutput = true;
|
||||
}
|
||||
@@ -325,8 +331,6 @@ function testFixtureStdio(
|
||||
|
||||
dev.on('error', () => {
|
||||
if (!printedOutput) {
|
||||
const stdout = Buffer.concat(stdoutList).toString();
|
||||
const stderr = Buffer.concat(stderrList).toString();
|
||||
printOutput(directory, stdout, stderr);
|
||||
printedOutput = true;
|
||||
}
|
||||
@@ -375,6 +379,153 @@ test.afterEach(async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('[vercel dev] prints `npm install` errors', async t => {
|
||||
const dir = fixture('runtime-not-installed');
|
||||
const result = await exec(dir);
|
||||
t.truthy(result.stderr.includes('npm ERR! 404'));
|
||||
t.truthy(
|
||||
result.stderr.includes('Failed to install `vercel dev` dependencies')
|
||||
);
|
||||
t.truthy(
|
||||
result.stderr.includes('https://vercel.link/npm-install-failed-dev')
|
||||
);
|
||||
});
|
||||
|
||||
test('[vercel dev] `vercel.json` should be invalidated if deleted', async t => {
|
||||
const dir = fixture('invalidate-vercel-config');
|
||||
const configPath = join(dir, 'vercel.json');
|
||||
const originalConfig = await fs.readJSON(configPath);
|
||||
const { dev, port, readyResolver } = await testFixture(dir);
|
||||
|
||||
try {
|
||||
await readyResolver;
|
||||
|
||||
{
|
||||
// Env var should be set from `vercel.json`
|
||||
const res = await fetch(`http://localhost:${port}/api`);
|
||||
const body = await res.json();
|
||||
t.is(body.FOO, 'bar');
|
||||
}
|
||||
|
||||
{
|
||||
// Env var should not be set after `vercel.json` is deleted
|
||||
await fs.remove(configPath);
|
||||
await sleep(1000);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/api`);
|
||||
const body = await res.json();
|
||||
t.is(body.FOO, undefined);
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
await fs.writeJSON(configPath, originalConfig);
|
||||
}
|
||||
});
|
||||
|
||||
test('[vercel dev] reflects changes to config and env without restart', async t => {
|
||||
const dir = fixture('node-helpers');
|
||||
const configPath = join(dir, 'vercel.json');
|
||||
const originalConfig = await fs.readJSON(configPath);
|
||||
const { dev, port, readyResolver } = await testFixture(dir);
|
||||
|
||||
try {
|
||||
await readyResolver;
|
||||
|
||||
{
|
||||
// Node.js helpers should be available by default
|
||||
const res = await fetch(`http://localhost:${port}/?foo=bar`);
|
||||
const body = await res.json();
|
||||
t.is(body.hasHelpers, true);
|
||||
t.is(body.query.foo, 'bar');
|
||||
}
|
||||
|
||||
{
|
||||
// Disable the helpers via `config.helpers = false`
|
||||
const config = {
|
||||
...originalConfig,
|
||||
builds: [
|
||||
{
|
||||
...originalConfig.builds[0],
|
||||
config: {
|
||||
helpers: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
await fs.writeJSON(configPath, config);
|
||||
await sleep(1000);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/?foo=bar`);
|
||||
const body = await res.json();
|
||||
t.is(body.hasHelpers, false);
|
||||
t.is(body.query, undefined);
|
||||
}
|
||||
|
||||
{
|
||||
// Enable the helpers via `config.helpers = true`
|
||||
const config = {
|
||||
...originalConfig,
|
||||
builds: [
|
||||
{
|
||||
...originalConfig.builds[0],
|
||||
config: {
|
||||
helpers: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
await fs.writeJSON(configPath, config);
|
||||
await sleep(1000);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/?foo=baz`);
|
||||
const body = await res.json();
|
||||
t.is(body.hasHelpers, true);
|
||||
t.is(body.query.foo, 'baz');
|
||||
}
|
||||
|
||||
{
|
||||
// Disable the helpers via `NODEJS_HELPERS = '0'`
|
||||
const config = {
|
||||
...originalConfig,
|
||||
build: {
|
||||
env: {
|
||||
NODEJS_HELPERS: '0',
|
||||
},
|
||||
},
|
||||
};
|
||||
await fs.writeJSON(configPath, config);
|
||||
await sleep(1000);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/?foo=baz`);
|
||||
const body = await res.json();
|
||||
t.is(body.hasHelpers, false);
|
||||
t.is(body.query, undefined);
|
||||
}
|
||||
|
||||
{
|
||||
// Enable the helpers via `NODEJS_HELPERS = '1'`
|
||||
const config = {
|
||||
...originalConfig,
|
||||
build: {
|
||||
env: {
|
||||
NODEJS_HELPERS: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
await fs.writeJSON(configPath, config);
|
||||
await sleep(1000);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/?foo=boo`);
|
||||
const body = await res.json();
|
||||
t.is(body.hasHelpers, true);
|
||||
t.is(body.query.foo, 'boo');
|
||||
}
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
await fs.writeJSON(configPath, originalConfig);
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'[vercel dev] validate routes that use `check: true`',
|
||||
testFixtureStdio('routes-check-true', async testPath => {
|
||||
@@ -391,6 +542,17 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] validate routes that use custom 404 page',
|
||||
testFixtureStdio('routes-custom-404', async testPath => {
|
||||
await testPath(200, '/', 'Home Page');
|
||||
await testPath(404, '/nothing', 'Custom User 404');
|
||||
await testPath(404, '/exact', 'Exact Custom 404');
|
||||
await testPath(200, '/api/hello', 'Hello');
|
||||
await testPath(404, '/api/nothing', 'Custom User 404');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] handles miss after route',
|
||||
testFixtureStdio('handle-miss-after-route', async testPath => {
|
||||
@@ -477,7 +639,8 @@ test(
|
||||
await testPath(200, '/api/date', /current date/);
|
||||
await testPath(200, '/api/rand', /random number/);
|
||||
await testPath(200, '/api/rand.js', /random number/);
|
||||
await testPath(404, '/api/api');
|
||||
await testPath(404, '/api/api', /NOT_FOUND/m);
|
||||
await testPath(404, '/nothing', /Custom 404 Page/);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -573,7 +736,11 @@ test('[vercel dev] validate mixed routes and rewrites', async t => {
|
||||
const output = await exec(directory);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(output.stderr, /Cannot define both `routes` and `rewrites`/m);
|
||||
t.regex(
|
||||
output.stderr,
|
||||
/If `rewrites`, `redirects`, `headers`, `cleanUrls` or `trailingSlash` are used, then `routes` cannot be present./m
|
||||
);
|
||||
t.regex(output.stderr, /vercel\.link\/mix-routing-props/m);
|
||||
});
|
||||
|
||||
// Test seems unstable: It won't return sometimes.
|
||||
@@ -672,6 +839,17 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
|
||||
testFixtureStdio('test-clean-urls-custom-404', async testPath => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about', 'The about page');
|
||||
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
|
||||
await testPath(404, '/nothing', 'Custom 404 Page');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
|
||||
testFixtureStdio('test-clean-urls-trailing-slash', async testPath => {
|
||||
@@ -740,6 +918,16 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
|
||||
testFixtureStdio('test-trailing-slash-custom-404', async testPath => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about.html', 'The about page');
|
||||
await testPath(200, '/contact/', 'Contact Subdirectory');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash false serve correct content',
|
||||
testFixtureStdio('test-trailing-slash-false', async testPath => {
|
||||
@@ -772,7 +960,11 @@ test(
|
||||
testFixtureStdio(
|
||||
'invalid-builder-routes',
|
||||
async testPath => {
|
||||
await testPath(500, '/', /Invalid regular expression/m);
|
||||
await testPath(
|
||||
500,
|
||||
'/',
|
||||
/Route at index 0 has invalid `src` regular expression/m
|
||||
);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
@@ -897,6 +1089,8 @@ test(
|
||||
'[vercel dev] 10-nextjs-node',
|
||||
testFixtureStdio('10-nextjs-node', async testPath => {
|
||||
await testPath(200, '/', /Next.js \+ Node.js API/m);
|
||||
await testPath(200, '/api/date', new RegExp(new Date().getFullYear()));
|
||||
await testPath(404, '/nothing', /Custom Next 404/);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1339,3 +1533,36 @@ test(
|
||||
await testPath(200, `/api/user.sh`, /Hello, from Bash!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should work with nested `tsconfig.json` files',
|
||||
testFixtureStdio('nested-tsconfig', async testPath => {
|
||||
await testPath(200, `/`, /Nested tsconfig.json test page/);
|
||||
await testPath(200, `/api`, 'Nested `tsconfig.json` API endpoint');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Should force `tsc` option "module: commonjs" for `startDevServer()`',
|
||||
testFixtureStdio('force-module-commonjs', async testPath => {
|
||||
await testPath(200, `/`, /Force "module: commonjs" test page/);
|
||||
await testPath(
|
||||
200,
|
||||
`/api`,
|
||||
'Force "module: commonjs" JavaScript with ES Modules API endpoint'
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
`/api/ts`,
|
||||
'Force "module: commonjs" TypeScript API endpoint'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should prioritize index.html over other file named index.*',
|
||||
testFixtureStdio('index-html-priority', async testPath => {
|
||||
await testPath(200, '/', 'This is index.html');
|
||||
await testPath(200, '/index.css', 'This is index.css');
|
||||
})
|
||||
);
|
||||
|
||||
2
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.gitignore
vendored
Normal file
2
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!.vercel
|
||||
!.now
|
||||
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.now/.gitkeep
vendored
Normal file
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.now/.gitkeep
vendored
Normal file
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.vercel/.gitkeep
vendored
Normal file
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-error/.vercel/.gitkeep
vendored
Normal file
1
packages/now-cli/test/fixtures/unit/get-vercel-directory-legacy/.gitignore
vendored
Normal file
1
packages/now-cli/test/fixtures/unit/get-vercel-directory-legacy/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.now
|
||||
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-legacy/.now/.gitkeep
vendored
Normal file
0
packages/now-cli/test/fixtures/unit/get-vercel-directory-legacy/.now/.gitkeep
vendored
Normal file
1
packages/now-cli/test/fixtures/unit/get-vercel-directory/.gitignore
vendored
Normal file
1
packages/now-cli/test/fixtures/unit/get-vercel-directory/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
0
packages/now-cli/test/fixtures/unit/get-vercel-directory/.vercel/.gitkeep
vendored
Normal file
0
packages/now-cli/test/fixtures/unit/get-vercel-directory/.vercel/.gitkeep
vendored
Normal file
@@ -514,6 +514,11 @@ CMD ["node", "index.js"]`,
|
||||
],
|
||||
}),
|
||||
},
|
||||
'conflicting-now-json-vercel-json': {
|
||||
'index.html': '<h1>I am a website.</h1>',
|
||||
'vercel.json': getConfigFile(true),
|
||||
'now.json': getConfigFile(true),
|
||||
},
|
||||
};
|
||||
|
||||
for (const typeName of Object.keys(spec)) {
|
||||
|
||||
2
packages/now-cli/test/integration-v1.js
vendored
2
packages/now-cli/test/integration-v1.js
vendored
@@ -1703,7 +1703,7 @@ test('ensure we are getting a warning for the old team flag', async t => {
|
||||
// Ensure the warning is printed
|
||||
t.true(
|
||||
stderr.includes(
|
||||
'WARN! The "--team" flag is deprecated. Please use "--scope" instead.'
|
||||
'WARN! The "--team" option is deprecated. Please use "--scope" instead.'
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
43
packages/now-cli/test/integration.js
vendored
43
packages/now-cli/test/integration.js
vendored
@@ -1589,7 +1589,7 @@ test('create a staging deployment', async t => {
|
||||
/Setting target to staging/gm,
|
||||
formatOutput(targetCall)
|
||||
);
|
||||
|
||||
t.regex(targetCall.stdout, /https:\/\//gm);
|
||||
t.is(targetCall.exitCode, 0, formatOutput(targetCall));
|
||||
|
||||
const { host } = new URL(targetCall.stdout);
|
||||
@@ -1625,6 +1625,7 @@ test('create a production deployment', async t => {
|
||||
/Setting target to production/gm,
|
||||
formatOutput(targetCall)
|
||||
);
|
||||
t.regex(targetCall.stdout, /https:\/\//gm);
|
||||
|
||||
const { host: targetHost } = new URL(targetCall.stdout);
|
||||
const targetDeployment = await apiFetch(
|
||||
@@ -1648,6 +1649,7 @@ test('create a production deployment', async t => {
|
||||
/Setting target to production/gm,
|
||||
formatOutput(targetCall)
|
||||
);
|
||||
t.regex(call.stdout, /https:\/\//gm);
|
||||
|
||||
const { host } = new URL(call.stdout);
|
||||
const deployment = await apiFetch(
|
||||
@@ -2554,7 +2556,7 @@ test('should prefill "project name" prompt with --name', async t => {
|
||||
let isDeprecated = false;
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
if (chunk.includes('The "--name" flag is deprecated')) {
|
||||
if (chunk.includes('The "--name" option is deprecated')) {
|
||||
isDeprecated = true;
|
||||
}
|
||||
|
||||
@@ -2895,3 +2897,40 @@ test('deploys with only vercel.json and README.md', async t => {
|
||||
const text = await res.text();
|
||||
t.regex(text, /readme contents/);
|
||||
});
|
||||
|
||||
test('reject conflicting `vercel.json` and `now.json` files', async t => {
|
||||
const directory = fixture('conflicting-now-json-vercel-json');
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
|
||||
t.true(
|
||||
stderr.includes(
|
||||
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.'
|
||||
),
|
||||
formatOutput({ stderr, stdout })
|
||||
);
|
||||
});
|
||||
|
||||
test('`vc --debug project ls` should output the projects listing', async t => {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--debug', 'project', 'ls'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
t.true(
|
||||
stdout.includes('> Projects found under'),
|
||||
formatOutput({ stderr, stdout })
|
||||
);
|
||||
});
|
||||
|
||||
29
packages/now-cli/test/unit.js
vendored
29
packages/now-cli/test/unit.js
vendored
@@ -1,4 +1,4 @@
|
||||
import { join, sep } from 'path';
|
||||
import { basename, join, sep } from 'path';
|
||||
import { send } from 'micro';
|
||||
import test from 'ava';
|
||||
import sinon from 'sinon';
|
||||
@@ -24,6 +24,7 @@ import { isValidName } from '../src/util/is-valid-name';
|
||||
import preferV2Deployment from '../src/util/prefer-v2-deployment';
|
||||
import getUpdateCommand from '../src/util/get-update-command';
|
||||
import { isCanary } from '../src/util/is-canary';
|
||||
import { getVercelDirectory } from '../src/util/projects/link';
|
||||
|
||||
const output = createOutput({ debug: false });
|
||||
const prefix = `${join(__dirname, 'fixtures', 'unit')}${sep}`;
|
||||
@@ -1091,3 +1092,29 @@ test('detect update command', async t => {
|
||||
const updateCommand = await getUpdateCommand();
|
||||
t.is(updateCommand, `yarn add vercel@${isCanary() ? 'canary' : 'latest'}`);
|
||||
});
|
||||
|
||||
test('`getVercelDirectory()` returns ".vercel"', t => {
|
||||
const cwd = fixture('get-vercel-directory');
|
||||
const dir = getVercelDirectory(cwd);
|
||||
t.is(basename(dir), '.vercel');
|
||||
});
|
||||
|
||||
test('`getVercelDirectory()` returns ".now"', t => {
|
||||
const cwd = fixture('get-vercel-directory-legacy');
|
||||
const dir = getVercelDirectory(cwd);
|
||||
t.is(basename(dir), '.now');
|
||||
});
|
||||
|
||||
test('`getVercelDirectory()` throws an error if ".vercel" and ".now" exist', t => {
|
||||
let err;
|
||||
const cwd = fixture('get-vercel-directory-error');
|
||||
try {
|
||||
getVercelDirectory(cwd);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
t.is(
|
||||
err.message,
|
||||
'Both `.vercel` and `.now` directories exist. Please remove the `.now` directory.'
|
||||
);
|
||||
});
|
||||
|
||||
2
packages/now-client/.gitignore
vendored
2
packages/now-client/.gitignore
vendored
@@ -4,3 +4,5 @@ node_modules
|
||||
*.log
|
||||
|
||||
!tests/fixtures/nowignore/node_modules
|
||||
!tests/fixtures/vercelignore-allow-nodemodules/node_modules
|
||||
!tests/fixtures/vercelignore-allow-nodemodules/sub/node_modules
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "8.0.2-canary.1",
|
||||
"version": "8.1.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -27,7 +27,7 @@
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@types/recursive-readdir": "2.2.0",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"typescript": "3.5.1"
|
||||
"typescript": "3.9.3"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
|
||||
@@ -93,6 +93,16 @@ export async function* checkDeploymentStatus(
|
||||
}
|
||||
|
||||
if (isAliasAssigned(deploymentUpdate)) {
|
||||
if (
|
||||
deploymentUpdate.aliasWarning &&
|
||||
deploymentUpdate.aliasWarning.message
|
||||
) {
|
||||
yield {
|
||||
type: 'warning',
|
||||
payload: deploymentUpdate.aliasWarning.message,
|
||||
};
|
||||
}
|
||||
|
||||
debug('Deployment alias assigned');
|
||||
return yield { type: 'alias-assigned', payload: deploymentUpdate };
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { readdir as readRootFolder, lstatSync } from 'fs-extra';
|
||||
import { relative, isAbsolute, basename } from 'path';
|
||||
import hashes, { mapToObject } from './utils/hashes';
|
||||
import { upload } from './upload';
|
||||
import { buildFileTree, createDebug, parseNowJSON } from './utils';
|
||||
import { buildFileTree, createDebug, parseVercelConfig } from './utils';
|
||||
import { DeploymentError } from './errors';
|
||||
import {
|
||||
NowConfig,
|
||||
@@ -85,10 +85,24 @@ export default function buildCreateDeployment(version: number) {
|
||||
let configPath: string | undefined;
|
||||
if (!nowConfig) {
|
||||
// If the user did not provide a config file, use the one in the root directory.
|
||||
configPath = fileList
|
||||
.map(f => relative(cwd, f))
|
||||
.find(f => f === 'vercel.json' || f === 'now.json');
|
||||
nowConfig = await parseNowJSON(configPath);
|
||||
const relativePaths = fileList.map(f => relative(cwd, f));
|
||||
const hasVercelConfig = relativePaths.includes('vercel.json');
|
||||
const hasNowConfig = relativePaths.includes('now.json');
|
||||
|
||||
if (hasVercelConfig) {
|
||||
if (hasNowConfig) {
|
||||
throw new DeploymentError({
|
||||
code: 'conflicting_config',
|
||||
message:
|
||||
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.',
|
||||
});
|
||||
}
|
||||
configPath = 'vercel.json';
|
||||
} else if (hasNowConfig) {
|
||||
configPath = 'now.json';
|
||||
}
|
||||
|
||||
nowConfig = await parseVercelConfig(configPath);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -7,6 +7,7 @@ import qs from 'querystring';
|
||||
import ignore from 'ignore';
|
||||
type Ignore = ReturnType<typeof ignore>;
|
||||
import { pkgVersion } from '../pkg';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import { NowClientOptions, DeploymentOptions, NowConfig } from '../types';
|
||||
import { Sema } from 'async-sema';
|
||||
import { readFile } from 'fs-extra';
|
||||
@@ -48,10 +49,10 @@ export function getApiDeploymentsUrl(
|
||||
return '/v10/now/deployments';
|
||||
}
|
||||
|
||||
return '/v12/now/deployments';
|
||||
return '/v13/now/deployments';
|
||||
}
|
||||
|
||||
export async function parseNowJSON(filePath?: string): Promise<NowConfig> {
|
||||
export async function parseVercelConfig(filePath?: string): Promise<NowConfig> {
|
||||
if (!filePath) {
|
||||
return {};
|
||||
}
|
||||
@@ -76,43 +77,22 @@ const maybeRead = async function<T>(path: string, default_: T) {
|
||||
}
|
||||
};
|
||||
|
||||
export async function readdirRelative(
|
||||
path: string,
|
||||
ignores: string[],
|
||||
cwd: string
|
||||
): Promise<string[]> {
|
||||
const preprocessedIgnores = ignores.map(ignore => {
|
||||
if (ignore.endsWith('/')) {
|
||||
return ignore.slice(0, -1);
|
||||
}
|
||||
return ignore;
|
||||
});
|
||||
const dirContents = await readdir(path, preprocessedIgnores);
|
||||
return dirContents.map(filePath => relative(cwd, filePath));
|
||||
}
|
||||
|
||||
export async function buildFileTree(
|
||||
path: string | string[],
|
||||
isDirectory: boolean,
|
||||
debug: Debug
|
||||
): Promise<string[]> {
|
||||
// Get .nowignore
|
||||
let { ig, ignores } = await getVercelIgnore(path);
|
||||
|
||||
debug(`Found ${ig.ignores.length} rules in .nowignore`);
|
||||
|
||||
let fileList: string[];
|
||||
let { ig } = await getVercelIgnore(path);
|
||||
|
||||
debug(`Found ${ig.ignores.length} rules in .vercelignore`);
|
||||
debug('Building file tree...');
|
||||
|
||||
if (isDirectory && !Array.isArray(path)) {
|
||||
// Directory path
|
||||
const cwd = process.cwd();
|
||||
const relativeFileList = await readdirRelative(path, ignores, cwd);
|
||||
fileList = ig
|
||||
.filter(relativeFileList)
|
||||
.map((relativePath: string) => join(cwd, relativePath));
|
||||
|
||||
const ignores = (absPath: string) => ig.ignores(relative(cwd, absPath));
|
||||
fileList = await readdir(path, [ignores]);
|
||||
debug(`Read ${fileList.length} files in the specified directory`);
|
||||
} else if (Array.isArray(path)) {
|
||||
// Array of file paths
|
||||
@@ -166,9 +146,12 @@ export async function getVercelIgnore(
|
||||
maybeRead(join(cwd, '.nowignore'), ''),
|
||||
]);
|
||||
if (vercelignore && nowignore) {
|
||||
throw new Error(
|
||||
'Cannot use both a `.vercelignore` and `.nowignore` file. Please delete the `.nowignore` file.'
|
||||
);
|
||||
throw new NowBuildError({
|
||||
code: 'CONFLICTING_IGNORE_FILES',
|
||||
message:
|
||||
'Cannot use both a `.vercelignore` and `.nowignore` file. Please delete the `.nowignore` file.',
|
||||
link: 'https://vercel.link/combining-old-and-new-config',
|
||||
});
|
||||
}
|
||||
return vercelignore || nowignore;
|
||||
})
|
||||
|
||||
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/.env.local
vendored
Normal file
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/.env.local
vendored
Normal file
@@ -0,0 +1 @@
|
||||
EXCLUDE="this file should be ignored"
|
||||
6
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/.vercelignore
vendored
Normal file
6
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/.vercelignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# literally dont ignore the node_modules directory
|
||||
# so basically include the node_modules directory recursively
|
||||
!node_modules/
|
||||
|
||||
# ignore this file in addition to the defaults
|
||||
exclude.txt
|
||||
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/exclude.txt
vendored
Normal file
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/exclude.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Should be ignored
|
||||
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/hello.txt
vendored
Normal file
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/hello.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello World
|
||||
0
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/node_modules/one.txt
generated
vendored
Normal file
0
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/node_modules/one.txt
generated
vendored
Normal file
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/sub/include.txt
vendored
Normal file
1
packages/now-client/tests/fixtures/vercelignore-allow-nodemodules/sub/include.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Should be included
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user