mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-27 11:49:14 +00:00
Compare commits
17 Commits
@now/go@0.
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e58951808 | ||
|
|
fbd805aad7 | ||
|
|
2a2705c6e3 | ||
|
|
986c957183 | ||
|
|
c5d063e876 | ||
|
|
500c36f5d4 | ||
|
|
69dbbeac44 | ||
|
|
69486c3adb | ||
|
|
e6692bb79b | ||
|
|
94fba1d7af | ||
|
|
223d8f4774 | ||
|
|
42e7a7e4e3 | ||
|
|
6716fdd49b | ||
|
|
3b69092fd8 | ||
|
|
aa8eaedbc8 | ||
|
|
f519ed373f | ||
|
|
851dff4b03 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "16.3.0",
|
||||
"version": "16.3.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
|
||||
@@ -10,7 +10,7 @@ import Client from '../util/client.ts';
|
||||
import logo from '../util/output/logo';
|
||||
import getScope from '../util/get-scope';
|
||||
|
||||
const e = encodeURIComponent
|
||||
const e = encodeURIComponent;
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -48,8 +48,8 @@ const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
alias: {
|
||||
help: 'h'
|
||||
}
|
||||
help: 'h',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -63,7 +63,10 @@ const main = async ctx => {
|
||||
await exit(0);
|
||||
}
|
||||
|
||||
const { authConfig: { token }, config: { currentTeam }} = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config: { currentTeam },
|
||||
} = ctx;
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
|
||||
const { contextName } = await getScope(client);
|
||||
@@ -93,17 +96,21 @@ async function run({ client, contextName }) {
|
||||
if (args.length !== 0) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan('`now projects ls`')}`
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
'`now projects ls`'
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const list = await client.fetch('/projects/list', {method: 'GET'});
|
||||
const list = await client.fetch('/v2/projects/', { method: 'GET' });
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
`> ${plural('project', list.length, true)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
`> ${plural('project', list.length, true)} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
|
||||
if (list.length > 0) {
|
||||
@@ -114,19 +121,19 @@ async function run({ client, contextName }) {
|
||||
header.concat(
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - new Date(secret.updatedAt)) } ago`)
|
||||
])
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - new Date(secret.updatedAt))} ago`),
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
if (out) {
|
||||
console.log(`\n${ out }\n`);
|
||||
console.log(`\n${out}\n`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -148,11 +155,11 @@ async function run({ client, contextName }) {
|
||||
|
||||
// Check the existence of the project
|
||||
try {
|
||||
await client.fetch(`/projects/info/${e(name)}`)
|
||||
} catch(err) {
|
||||
await client.fetch(`/projects/info/${e(name)}`);
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
console.error(error('No such project exists'))
|
||||
return exit(1)
|
||||
console.error(error('No such project exists'));
|
||||
return exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +169,9 @@ async function run({ client, contextName }) {
|
||||
return exit(0);
|
||||
}
|
||||
|
||||
await client.fetch('/projects/remove', {method: 'DELETE', body: {name}});
|
||||
await client.fetch(`/v2/projects/${name}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||
@@ -193,7 +202,10 @@ async function run({ client, contextName }) {
|
||||
}
|
||||
|
||||
const [name] = args;
|
||||
await client.fetch('/projects/ensure-project', {method: 'POST', body: {name}});
|
||||
await client.fetch('/projects/ensure-project', {
|
||||
method: 'POST',
|
||||
body: { name },
|
||||
});
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
@@ -204,9 +216,7 @@ async function run({ client, contextName }) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(
|
||||
error('Please specify a valid subcommand: ls | add | rm')
|
||||
);
|
||||
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||
help();
|
||||
exit(1);
|
||||
}
|
||||
@@ -220,7 +230,7 @@ function readConfirmation(projectName) {
|
||||
return new Promise(resolve => {
|
||||
process.stdout.write(
|
||||
`The project: ${chalk.bold(projectName)} will be removed permanently.\n` +
|
||||
`It will also delete everything under the project including deployments.\n`
|
||||
`It will also delete everything under the project including deployments.\n`
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
|
||||
@@ -5,13 +5,17 @@ import pluralize from 'pluralize';
|
||||
import {
|
||||
createDeployment,
|
||||
createLegacyDeployment,
|
||||
DeploymentOptions,
|
||||
} from '../../../../now-client';
|
||||
import wait from '../output/wait';
|
||||
import createOutput from '../output';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { NowConfig } from '../dev/types';
|
||||
|
||||
export default async function processDeployment({
|
||||
now,
|
||||
debug,
|
||||
output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
@@ -20,18 +24,34 @@ export default async function processDeployment({
|
||||
legacy,
|
||||
env,
|
||||
quiet,
|
||||
}: any) {
|
||||
const { warn, log } = createOutput({ debug });
|
||||
nowConfig,
|
||||
}: {
|
||||
now: Now;
|
||||
output: Output;
|
||||
hashes: { [key: string]: any };
|
||||
paths: string[];
|
||||
requestBody: DeploymentOptions;
|
||||
uploadStamp: () => number;
|
||||
deployStamp: () => number;
|
||||
legacy: boolean;
|
||||
env: any;
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
}) {
|
||||
const { warn, log, debug, note } = output;
|
||||
let bar: Progress | null = null;
|
||||
|
||||
const path0 = paths[0];
|
||||
const opts: DeploymentOptions = {
|
||||
...requestBody,
|
||||
debug: now._debug,
|
||||
};
|
||||
|
||||
if (!legacy) {
|
||||
let buildSpinner = null;
|
||||
let deploySpinner = null;
|
||||
|
||||
for await (const event of createDeployment(paths[0], {
|
||||
...requestBody,
|
||||
debug: now._debug,
|
||||
})) {
|
||||
for await (const event of createDeployment(path0, opts, nowConfig)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
@@ -40,6 +60,10 @@ export default async function processDeployment({
|
||||
warn(event.payload);
|
||||
}
|
||||
|
||||
if (event.type === 'notice') {
|
||||
note(event.payload);
|
||||
}
|
||||
|
||||
if (event.type === 'file_count') {
|
||||
debug(
|
||||
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||
@@ -128,10 +152,7 @@ export default async function processDeployment({
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for await (const event of createLegacyDeployment(paths[0], {
|
||||
...requestBody,
|
||||
debug: now._debug,
|
||||
})) {
|
||||
for await (const event of createLegacyDeployment(path0, opts, nowConfig)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export default async function getConfig(output: Output, configFile?: string) {
|
||||
if (config) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// First try with the config supplied by the user via --local-config
|
||||
if (configFile) {
|
||||
const localFilePath = path.resolve(localPath, configFile);
|
||||
@@ -27,8 +26,7 @@ export default async function getConfig(output: Output, configFile?: string) {
|
||||
return localConfig;
|
||||
}
|
||||
if (localConfig !== null) {
|
||||
const castedConfig = localConfig;
|
||||
config = castedConfig;
|
||||
config = localConfig;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,13 +142,14 @@ export default class Now extends EventEmitter {
|
||||
if (isBuilds) {
|
||||
deployment = await processDeployment({
|
||||
now: this,
|
||||
debug,
|
||||
output: this._output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
quiet,
|
||||
nowConfig,
|
||||
});
|
||||
} else {
|
||||
// Read `registry.npmjs.org` authToken from .npmrc
|
||||
@@ -183,7 +184,7 @@ export default class Now extends EventEmitter {
|
||||
deployment = await processDeployment({
|
||||
legacy: true,
|
||||
now: this,
|
||||
debug,
|
||||
output: this._output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
@@ -191,6 +192,7 @@ export default class Now extends EventEmitter {
|
||||
deployStamp,
|
||||
quiet,
|
||||
env,
|
||||
nowConfig,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -377,7 +379,7 @@ export default class Now extends EventEmitter {
|
||||
if (!app && !Object.keys(meta).length) {
|
||||
// Get the 35 latest projects and their latest deployment
|
||||
const query = new URLSearchParams({ limit: 35 });
|
||||
const projects = await fetchRetry(`/projects/list?${query}`);
|
||||
const projects = await fetchRetry(`/v2/projects/?${query}`);
|
||||
|
||||
const deployments = await Promise.all(
|
||||
projects.map(async ({ id: projectId }) => {
|
||||
|
||||
2
packages/now-cli/test/dev/fixtures/25-nextjs-src-dir/.gitignore
vendored
Normal file
2
packages/now-cli/test/dev/fixtures/25-nextjs-src-dir/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.next
|
||||
@@ -0,0 +1,2 @@
|
||||
README.md
|
||||
yarn.lock
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "nextjs",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^9.1.1",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
|
||||
function Index() {
|
||||
const [date, setDate] = useState(null);
|
||||
useEffect(() => {
|
||||
async function getDate() {
|
||||
const res = await fetch('/api/date');
|
||||
const newDate = await res.text();
|
||||
setDate(newDate);
|
||||
}
|
||||
getDate();
|
||||
}, []);
|
||||
return (
|
||||
<main>
|
||||
<Head>
|
||||
<title>Next.js + Node API</title>
|
||||
</Head>
|
||||
<h1>Next.js + Node.js API</h1>
|
||||
<h2>
|
||||
Deployed with{' '}
|
||||
<a
|
||||
href="https://zeit.co/docs"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
ZEIT Now
|
||||
</a>
|
||||
!
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/zeit/now-examples/blob/master/nextjs-node"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
This project
|
||||
</a>{' '}
|
||||
is a <a href="https://nextjs.org/">Next.js</a> app with two directories,{' '}
|
||||
<code>/pages</code> for static content and <code>/api</code> which
|
||||
contains a serverless <a href="https://nodejs.org/en/">Node.js</a>{' '}
|
||||
function. See{' '}
|
||||
<a href="/api/date">
|
||||
<code>api/date</code> for the Date API with Node.js
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<br />
|
||||
<h2>The date according to Node.js is:</h2>
|
||||
<p>{date ? date : 'Loading date...'}</p>
|
||||
<style jsx>{`
|
||||
main {
|
||||
align-content: center;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue',
|
||||
'Helvetica', 'Arial', sans-serif;
|
||||
hyphens: auto;
|
||||
line-height: 1.65;
|
||||
margin: 0 auto;
|
||||
max-width: 680px;
|
||||
min-height: 100vh;
|
||||
padding: 72px 0;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-size: 45px;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
a {
|
||||
border-bottom: 1px solid white;
|
||||
color: #0076ff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
a:hover {
|
||||
border-bottom: 1px solid #0076ff;
|
||||
}
|
||||
code,
|
||||
pre {
|
||||
color: #d400ff;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
|
||||
DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace,
|
||||
serif;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
code:before,
|
||||
code:after {
|
||||
content: '\`';
|
||||
}
|
||||
`}</style>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
5467
packages/now-cli/test/dev/fixtures/25-nextjs-src-dir/yarn.lock
Normal file
5467
packages/now-cli/test/dev/fixtures/25-nextjs-src-dir/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -869,3 +869,23 @@ test('[now dev] do not rebuild for changes in the output directory', async t =>
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[now dev] 25-nextjs-src-dir', async t => {
|
||||
const directory = fixture('25-nextjs-src-dir');
|
||||
const { dev, port } = await testFixture(directory);
|
||||
|
||||
try {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const result = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
const response = await result;
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Next.js \+ Node.js API/gm);
|
||||
} finally {
|
||||
dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -267,6 +267,69 @@ module.exports = (req, res) => {
|
||||
Object.assign(JSON.parse(getConfigFile(true)), { alias: 'zeit.co' })
|
||||
),
|
||||
},
|
||||
'local-config-cloud-v1': {
|
||||
'.gitignore': '*.html',
|
||||
'index.js': `
|
||||
const { createServer } = require('http');
|
||||
const { readFileSync } = require('fs');
|
||||
const svr = createServer((req, res) => {
|
||||
const { url = '/' } = req;
|
||||
const file = '.' + url;
|
||||
console.log('reading file ' + file);
|
||||
try {
|
||||
let contents = readFileSync(file, 'utf8');
|
||||
res.end(contents || '');
|
||||
} catch (e) {
|
||||
res.statusCode = 404;
|
||||
res.end('Not found');
|
||||
}
|
||||
});
|
||||
svr.listen(3000);`,
|
||||
'main.html': '<h1>hello main</h1>',
|
||||
'test.html': '<h1>hello test</h1>',
|
||||
'folder/file1.txt': 'file1',
|
||||
'folder/sub/file2.txt': 'file2',
|
||||
Dockerfile: `FROM mhart/alpine-node:latest
|
||||
LABEL name "now-cli-dockerfile-${session}"
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN yarn
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "index.js"]`,
|
||||
'now.json': JSON.stringify({
|
||||
version: 1,
|
||||
type: 'docker',
|
||||
features: {
|
||||
cloud: 'v1',
|
||||
},
|
||||
files: ['.gitignore', 'folder', 'index.js', 'main.html'],
|
||||
}),
|
||||
'now-test.json': JSON.stringify({
|
||||
version: 1,
|
||||
type: 'docker',
|
||||
features: {
|
||||
cloud: 'v1',
|
||||
},
|
||||
files: ['.gitignore', 'folder', 'index.js', 'test.html'],
|
||||
}),
|
||||
},
|
||||
'local-config-v2': {
|
||||
[`main-${session}.html`]: '<h1>hello main</h1>',
|
||||
[`test-${session}.html`]: '<h1>hello test</h1>',
|
||||
'now.json': JSON.stringify({
|
||||
version: 2,
|
||||
builds: [{ src: `main-${session}.html`, use: '@now/static' }],
|
||||
routes: [{ src: '/another-main', dest: `/main-${session}.html` }],
|
||||
}),
|
||||
'now-test.json': JSON.stringify({
|
||||
version: 2,
|
||||
builds: [{ src: `test-${session}.html`, use: '@now/static' }],
|
||||
routes: [{ src: '/another-test', dest: `/test-${session}.html` }],
|
||||
}),
|
||||
},
|
||||
'alias-rules': {
|
||||
'rules.json': JSON.stringify({
|
||||
rules: [
|
||||
|
||||
81
packages/now-cli/test/integration.js
vendored
81
packages/now-cli/test/integration.js
vendored
@@ -201,6 +201,77 @@ test('login', async t => {
|
||||
t.is(typeof token, 'string');
|
||||
});
|
||||
|
||||
test('deploy using --local-config flag v2', async t => {
|
||||
const target = fixture('local-config-v2');
|
||||
|
||||
const { stdout, stderr, code } = await execa(
|
||||
binaryPath,
|
||||
['deploy', '--local-config', 'now-test.json', ...defaultArgs],
|
||||
{
|
||||
cwd: target,
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(code);
|
||||
|
||||
t.is(code, 0);
|
||||
|
||||
const { host } = new URL(stdout);
|
||||
|
||||
const testRes = await fetch(`https://${host}/test-${contextName}.html`);
|
||||
const testText = await testRes.text();
|
||||
t.is(testText, '<h1>hello test</h1>');
|
||||
|
||||
const anotherTestRes = await fetch(`https://${host}/another-test`);
|
||||
const anotherTestText = await anotherTestRes.text();
|
||||
t.is(anotherTestText, testText);
|
||||
|
||||
const mainRes = await fetch(`https://${host}/main-${contextName}.html`);
|
||||
t.is(mainRes.status, 404, 'Should not deploy/build main now.json');
|
||||
|
||||
const anotherMainRes = await fetch(`https://${host}/another-main`);
|
||||
t.is(anotherMainRes.status, 404, 'Should not deploy/build main now.json');
|
||||
});
|
||||
|
||||
test('deploy using --local-config flag type cloud v1', async t => {
|
||||
const target = fixture('local-config-cloud-v1');
|
||||
|
||||
const { stdout, stderr, code } = await execa(
|
||||
binaryPath,
|
||||
['deploy', '--public', '--local-config', 'now-test.json', ...defaultArgs],
|
||||
{
|
||||
cwd: target,
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(code);
|
||||
|
||||
t.is(code, 0);
|
||||
|
||||
const { host } = new URL(stdout);
|
||||
|
||||
const testRes = await fetch(`https://${host}/test.html`);
|
||||
const testText = await testRes.text();
|
||||
t.is(testText, '<h1>hello test</h1>');
|
||||
|
||||
const file1Res = await fetch(`https://${host}/folder/file1.txt`);
|
||||
const file1Text = await file1Res.text();
|
||||
t.is(file1Text, 'file1');
|
||||
|
||||
const file2Res = await fetch(`https://${host}/folder/sub/file2.txt`);
|
||||
const file2Text = await file2Res.text();
|
||||
t.is(file2Text, 'file2');
|
||||
|
||||
const mainRes = await fetch(`https://${host}/main.html`);
|
||||
t.is(mainRes.status, 404, 'Should not deploy/build main now.json');
|
||||
});
|
||||
|
||||
test('print the deploy help message', async t => {
|
||||
const { stderr, stdout, code } = await execa(
|
||||
binaryPath,
|
||||
@@ -695,7 +766,10 @@ test('ignore files specified in .nowignore', async t => {
|
||||
const directory = fixture('nowignore');
|
||||
|
||||
const args = ['--debug', '--public', '--name', session, ...defaultArgs];
|
||||
const targetCall = await execa(binaryPath, args, { cwd: directory, reject: false });
|
||||
const targetCall = await execa(binaryPath, args, {
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
});
|
||||
|
||||
console.log(targetCall.stderr);
|
||||
console.log(targetCall.stdout);
|
||||
@@ -713,7 +787,10 @@ test('ignore files specified in .nowignore via allowlist', async t => {
|
||||
const directory = fixture('nowignore-allowlist');
|
||||
|
||||
const args = ['--debug', '--public', '--name', session, ...defaultArgs];
|
||||
const targetCall = await execa(binaryPath, args, { cwd: directory, reject: false });
|
||||
const targetCall = await execa(binaryPath, args, {
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
});
|
||||
|
||||
console.log(targetCall.stderr);
|
||||
console.log(targetCall.stdout);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now-client",
|
||||
"version": "5.1.4",
|
||||
"version": "5.2.0",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,9 +4,13 @@ import readdir from 'recursive-readdir';
|
||||
import { relative, join } from 'path';
|
||||
import hashes, { mapToObject } from './utils/hashes';
|
||||
import uploadAndDeploy from './upload';
|
||||
import { getNowIgnore, createDebug } from './utils';
|
||||
import { getNowIgnore, createDebug, parseNowJSON } from './utils';
|
||||
import { DeploymentError } from './errors';
|
||||
import { CreateDeploymentFunction, DeploymentOptions } from './types';
|
||||
import {
|
||||
CreateDeploymentFunction,
|
||||
DeploymentOptions,
|
||||
NowJsonOptions,
|
||||
} from './types';
|
||||
|
||||
export { EVENTS } from './utils';
|
||||
|
||||
@@ -15,9 +19,11 @@ export default function buildCreateDeployment(
|
||||
): CreateDeploymentFunction {
|
||||
return async function* createDeployment(
|
||||
path: string | string[],
|
||||
options: DeploymentOptions = {}
|
||||
options: DeploymentOptions = {},
|
||||
nowConfig?: NowJsonOptions
|
||||
): AsyncIterableIterator<any> {
|
||||
const debug = createDebug(options.debug);
|
||||
const cwd = process.cwd();
|
||||
|
||||
debug('Creating deployment...');
|
||||
|
||||
@@ -89,6 +95,47 @@ export default function buildCreateDeployment(
|
||||
debug(`Deploying the provided path as single file`);
|
||||
}
|
||||
|
||||
if (!nowConfig) {
|
||||
// If the user did not provide a nowConfig,
|
||||
// then use the now.json file in the root.
|
||||
const fileName = 'now.json';
|
||||
const absolutePath = fileList.find(f => relative(cwd, f) === fileName);
|
||||
debug(absolutePath ? `Found ${fileName}` : `Missing ${fileName}`);
|
||||
nowConfig = await parseNowJSON(absolutePath);
|
||||
}
|
||||
|
||||
if (
|
||||
version === 1 &&
|
||||
nowConfig &&
|
||||
Array.isArray(nowConfig.files) &&
|
||||
nowConfig.files.length > 0
|
||||
) {
|
||||
// See the docs: https://zeit.co/docs/v1/features/configuration/#files-(array)
|
||||
debug('Filtering file list based on `files` key in now.json');
|
||||
const allowedFiles = new Set<string>(['Dockerfile']);
|
||||
const allowedDirs = new Set<string>();
|
||||
nowConfig.files.forEach(relPath => {
|
||||
if (lstatSync(relPath).isDirectory()) {
|
||||
allowedDirs.add(relPath);
|
||||
} else {
|
||||
allowedFiles.add(relPath);
|
||||
}
|
||||
});
|
||||
fileList = fileList.filter(absPath => {
|
||||
const relPath = relative(cwd, absPath);
|
||||
if (allowedFiles.has(relPath)) {
|
||||
return true;
|
||||
}
|
||||
for (let dir of allowedDirs) {
|
||||
if (relPath.startsWith(dir + '/')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
debug(`Found ${fileList.length} files: ${JSON.stringify(fileList)}`);
|
||||
}
|
||||
|
||||
// This is a useful warning because it prevents people
|
||||
// from getting confused about a deployment that renders 404.
|
||||
if (
|
||||
@@ -133,6 +180,7 @@ export default function buildCreateDeployment(
|
||||
const deploymentOpts = {
|
||||
debug: debug_,
|
||||
totalFiles: files.size,
|
||||
nowConfig,
|
||||
token,
|
||||
isDirectory,
|
||||
path,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import {
|
||||
parseNowJSON,
|
||||
fetch,
|
||||
API_DEPLOYMENTS,
|
||||
prepareFiles,
|
||||
@@ -22,6 +21,7 @@ export interface Options {
|
||||
defaultName?: string;
|
||||
preflight?: boolean;
|
||||
debug?: boolean;
|
||||
nowConfig?: NowJsonOptions;
|
||||
}
|
||||
|
||||
async function* createDeployment(
|
||||
@@ -73,6 +73,10 @@ async function* createDeployment(
|
||||
debug('Deployment created with a warning:', value);
|
||||
yield { type: 'warning', payload: value };
|
||||
}
|
||||
if (name.startsWith('x-now-notice-')) {
|
||||
debug('Deployment created with a notice:', value);
|
||||
yield { type: 'notice', payload: value };
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: 'created', payload: json };
|
||||
@@ -108,32 +112,12 @@ export default async function* deploy(
|
||||
options: Options
|
||||
): AsyncIterableIterator<{ type: string; payload: any }> {
|
||||
const debug = createDebug(options.debug);
|
||||
delete options.debug;
|
||||
|
||||
debug(`Trying to read 'now.json'`);
|
||||
const nowJson: DeploymentFile | undefined = Array.from(files.values()).find(
|
||||
(file: DeploymentFile): boolean => {
|
||||
return Boolean(
|
||||
file.names.find((name: string): boolean => name.includes('now.json'))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
debug(`'now.json' ${nowJson ? 'found' : "doesn't exist"}`);
|
||||
|
||||
const nowJsonMetadata: NowJsonOptions = parseNowJSON(nowJson);
|
||||
|
||||
const nowJsonMetadata = options.nowConfig || {};
|
||||
delete nowJsonMetadata.github;
|
||||
delete nowJsonMetadata.scope;
|
||||
|
||||
const meta = options.metadata || {};
|
||||
const metadata = { ...nowJsonMetadata, ...meta };
|
||||
if (nowJson) {
|
||||
debug(
|
||||
`Merged 'now.json' metadata and locally provided metadata:`,
|
||||
JSON.stringify(metadata)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we should default to a static deployment
|
||||
if (!metadata.version && !metadata.name) {
|
||||
|
||||
@@ -124,9 +124,11 @@ export interface NowJsonOptions {
|
||||
scope?: string;
|
||||
type?: 'NPM' | 'STATIC' | 'DOCKER';
|
||||
version?: number;
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
export type CreateDeploymentFunction = (
|
||||
path: string | string[],
|
||||
options?: DeploymentOptions
|
||||
options?: DeploymentOptions,
|
||||
nowConfig?: NowJsonOptions
|
||||
) => AsyncIterableIterator<any>;
|
||||
|
||||
@@ -26,7 +26,7 @@ const isClientNetworkError = (err: Error | DeploymentError) => {
|
||||
|
||||
export default async function* upload(
|
||||
files: Map<string, DeploymentFile>,
|
||||
options: Options
|
||||
options: Options,
|
||||
): AsyncIterableIterator<any> {
|
||||
const { token, teamId, debug: isDebug } = options;
|
||||
const debug = createDebug(isDebug);
|
||||
|
||||
@@ -32,13 +32,13 @@ export const EVENTS = new Set([
|
||||
'build-state-changed',
|
||||
]);
|
||||
|
||||
export function parseNowJSON(file?: DeploymentFile): NowJsonOptions {
|
||||
if (!file) {
|
||||
export async function parseNowJSON(filePath?: string): Promise<NowJsonOptions> {
|
||||
if (!filePath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonString = file.data.toString();
|
||||
const jsonString = await readFile(filePath, 'utf8');
|
||||
|
||||
return JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
|
||||
|
||||
@@ -37,12 +37,10 @@ import {
|
||||
EnvConfig,
|
||||
excludeFiles,
|
||||
ExperimentalTraceVersion,
|
||||
filesFromDirectory,
|
||||
getDynamicRoutes,
|
||||
getNextConfig,
|
||||
getPathsInside,
|
||||
getRoutes,
|
||||
includeOnlyEntryDirectory,
|
||||
isDynamicRoute,
|
||||
normalizePackageJson,
|
||||
normalizePage,
|
||||
@@ -69,6 +67,20 @@ interface BuildParamsType extends BuildOptions {
|
||||
|
||||
export const version = 2;
|
||||
|
||||
const nowDevChildProcesses = new Set<ChildProcess>();
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(signal => {
|
||||
process.once(signal as NodeJS.Signals, () => {
|
||||
for (const child of nowDevChildProcesses) {
|
||||
debug(
|
||||
`Got ${signal}, killing dev server child process (pid=${child.pid})`
|
||||
);
|
||||
process.kill(child.pid, signal);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Read package.json from files
|
||||
*/
|
||||
@@ -214,6 +226,7 @@ export const build = async ({
|
||||
const { forked, getUrl } = startDevServer(entryPath, runtimeEnv);
|
||||
urls[entrypoint] = await getUrl();
|
||||
childProcess = forked;
|
||||
nowDevChildProcesses.add(forked);
|
||||
debug(
|
||||
`${name} Development server for ${entrypoint} running at ${urls[entrypoint]}`
|
||||
);
|
||||
@@ -337,7 +350,7 @@ export const build = async ({
|
||||
if (isLegacy) {
|
||||
const filesAfterBuild = await glob('**', entryPath);
|
||||
|
||||
debug('Preparing lambda files...');
|
||||
debug('Preparing serverless function files...');
|
||||
let buildId: string;
|
||||
try {
|
||||
buildId = await readFile(
|
||||
@@ -405,7 +418,7 @@ export const build = async ({
|
||||
],
|
||||
};
|
||||
|
||||
debug(`Creating lambda for page: "${page}"...`);
|
||||
debug(`Creating serverless function for page: "${page}"...`);
|
||||
lambdas[path.join(entryDirectory, pathname)] = await createLambda({
|
||||
files: {
|
||||
...nextFiles,
|
||||
@@ -415,11 +428,11 @@ export const build = async ({
|
||||
handler: 'now__launcher.launcher',
|
||||
runtime: nodeVersion.runtime,
|
||||
});
|
||||
debug(`Created lambda for page: "${page}"`);
|
||||
debug(`Created serverless function for page: "${page}"`);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
debug('Preparing lambda files...');
|
||||
debug('Preparing serverless function files...');
|
||||
const pagesDir = path.join(entryPath, '.next', 'serverless', 'pages');
|
||||
|
||||
const pages = await glob('**/*.js', pagesDir);
|
||||
@@ -496,7 +509,8 @@ export const build = async ({
|
||||
} = {};
|
||||
|
||||
if (requiresTracing) {
|
||||
const tracingLabel = 'Tracing Next.js lambdas for external files ...';
|
||||
const tracingLabel =
|
||||
'Tracing Next.js serverless functions for external files ...';
|
||||
console.time(tracingLabel);
|
||||
|
||||
const apiPages: string[] = [];
|
||||
@@ -542,7 +556,7 @@ export const build = async ({
|
||||
apiFileList.forEach(collectTracedFiles(apiReasons, apiTracedFiles));
|
||||
console.timeEnd(tracingLabel);
|
||||
|
||||
const zippingLabel = 'Compressing shared lambda files';
|
||||
const zippingLabel = 'Compressing shared serverless function files';
|
||||
console.time(zippingLabel);
|
||||
|
||||
pseudoLayers.push(await createPseudoLayer(tracedFiles));
|
||||
@@ -560,7 +574,9 @@ export const build = async ({
|
||||
|
||||
const assetKeys = Object.keys(assets);
|
||||
if (assetKeys.length > 0) {
|
||||
debug('detected (legacy) assets to be bundled with lambda:');
|
||||
debug(
|
||||
'detected (legacy) assets to be bundled with serverless function:'
|
||||
);
|
||||
assetKeys.forEach(assetFile => debug(`\t${assetFile}`));
|
||||
debug(
|
||||
'\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.'
|
||||
@@ -570,7 +586,7 @@ export const build = async ({
|
||||
|
||||
const launcherPath = path.join(__dirname, 'templated-launcher.js');
|
||||
const launcherData = await readFile(launcherPath, 'utf8');
|
||||
const allLambdasLabel = `All lambdas created`;
|
||||
const allLambdasLabel = `All serverless functions created (in parallel)`;
|
||||
console.time(allLambdasLabel);
|
||||
|
||||
await Promise.all(
|
||||
@@ -586,7 +602,7 @@ export const build = async ({
|
||||
dynamicPages.push(normalizePage(pathname));
|
||||
}
|
||||
|
||||
const label = `Creating lambda for page: "${page}"...`;
|
||||
const label = `Created serverless function for "${page}" in`;
|
||||
console.time(label);
|
||||
|
||||
const pageFileName = path.normalize(
|
||||
@@ -637,6 +653,9 @@ export const build = async ({
|
||||
'**',
|
||||
path.join(entryPath, '.next', 'static')
|
||||
);
|
||||
const staticFolderFiles = await glob('**', path.join(entryPath, 'static'));
|
||||
const publicFolderFiles = await glob('**', path.join(entryPath, 'public'));
|
||||
|
||||
const staticFiles = Object.keys(nextStaticFiles).reduce(
|
||||
(mappedFiles, file) => ({
|
||||
...mappedFiles,
|
||||
@@ -646,23 +665,24 @@ export const build = async ({
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const entryDirectoryFiles = includeOnlyEntryDirectory(files, entryDirectory);
|
||||
const staticDirectoryFiles = filesFromDirectory(
|
||||
entryDirectoryFiles,
|
||||
path.join(entryDirectory, 'static')
|
||||
);
|
||||
const publicDirectoryFiles = filesFromDirectory(
|
||||
entryDirectoryFiles,
|
||||
path.join(entryDirectory, 'public')
|
||||
);
|
||||
const publicFiles = Object.keys(publicDirectoryFiles).reduce(
|
||||
const staticDirectoryFiles = Object.keys(staticFolderFiles).reduce(
|
||||
(mappedFiles, file) => ({
|
||||
...mappedFiles,
|
||||
[file.replace(/public[/\\]+/, '')]: publicDirectoryFiles[file],
|
||||
[path.join(entryDirectory, 'static', file)]: staticFolderFiles[file],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
const publicDirectoryFiles = Object.keys(publicFolderFiles).reduce(
|
||||
(mappedFiles, file) => ({
|
||||
...mappedFiles,
|
||||
[path.join(
|
||||
entryDirectory,
|
||||
file.replace(/public[/\\]+/, '')
|
||||
)]: publicFolderFiles[file],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
let dynamicPrefix = path.join('/', entryDirectory);
|
||||
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
|
||||
|
||||
@@ -682,7 +702,7 @@ export const build = async ({
|
||||
|
||||
return {
|
||||
output: {
|
||||
...publicFiles,
|
||||
...publicDirectoryFiles,
|
||||
...lambdas,
|
||||
...staticPages,
|
||||
...staticFiles,
|
||||
|
||||
@@ -60,24 +60,6 @@ function excludeFiles(
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Files object holding only the entrypoint files
|
||||
*/
|
||||
function includeOnlyEntryDirectory(
|
||||
files: Files,
|
||||
entryDirectory: string
|
||||
): Files {
|
||||
if (entryDirectory === '.') {
|
||||
return files;
|
||||
}
|
||||
|
||||
function matcher(filePath: string) {
|
||||
return !filePath.startsWith(entryDirectory);
|
||||
}
|
||||
|
||||
return excludeFiles(files, matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude package manager lockfiles from files
|
||||
*/
|
||||
@@ -92,17 +74,6 @@ function excludeLockFiles(files: Files): Files {
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include only the files from a selected directory
|
||||
*/
|
||||
function filesFromDirectory(files: Files, dir: string): Files {
|
||||
function matcher(filePath: string) {
|
||||
return !filePath.startsWith(dir.replace(/\\/g, '/'));
|
||||
}
|
||||
|
||||
return excludeFiles(files, matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce specific package.json configuration for smallest possible lambda
|
||||
*/
|
||||
@@ -205,17 +176,35 @@ function getRoutes(
|
||||
files: Files,
|
||||
url: string
|
||||
): Route[] {
|
||||
let pagesDir = '';
|
||||
const filesInside: Files = {};
|
||||
const prefix = entryDirectory === `.` ? `/` : `/${entryDirectory}/`;
|
||||
const fileKeys = Object.keys(files);
|
||||
|
||||
for (const file of Object.keys(files)) {
|
||||
for (const file of fileKeys) {
|
||||
if (!pathsInside.includes(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pagesDir) {
|
||||
if (file.startsWith(path.join(entryDirectory, 'pages'))) {
|
||||
pagesDir = 'pages';
|
||||
}
|
||||
}
|
||||
|
||||
filesInside[file] = files[file];
|
||||
}
|
||||
|
||||
// If default pages dir isn't found check for `src/pages`
|
||||
if (
|
||||
!pagesDir &&
|
||||
fileKeys.some(file =>
|
||||
file.startsWith(path.join(entryDirectory, 'src/pages'))
|
||||
)
|
||||
) {
|
||||
pagesDir = 'src/pages';
|
||||
}
|
||||
|
||||
const routes: Route[] = [
|
||||
{
|
||||
src: `${prefix}_next/(.*)`,
|
||||
@@ -231,13 +220,13 @@ function getRoutes(
|
||||
|
||||
for (const file of filePaths) {
|
||||
const relativePath = path.relative(entryDirectory, file);
|
||||
const isPage = pathIsInside('pages', relativePath);
|
||||
const isPage = pathIsInside(pagesDir, relativePath);
|
||||
|
||||
if (!isPage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relativeToPages = path.relative('pages', relativePath);
|
||||
const relativeToPages = path.relative(pagesDir, relativePath);
|
||||
const extension = path.extname(relativeToPages);
|
||||
const pageName = relativeToPages.replace(extension, '').replace(/\\/g, '/');
|
||||
|
||||
@@ -484,10 +473,8 @@ export async function createLambdaFromPseudoLayers({
|
||||
export {
|
||||
excludeFiles,
|
||||
validateEntrypoint,
|
||||
includeOnlyEntryDirectory,
|
||||
excludeLockFiles,
|
||||
normalizePackageJson,
|
||||
filesFromDirectory,
|
||||
getNextConfig,
|
||||
getPathsInside,
|
||||
getRoutes,
|
||||
|
||||
@@ -122,6 +122,7 @@ it(
|
||||
buildResult: { output },
|
||||
} = await runBuildLambda(path.join(__dirname, 'public-files'));
|
||||
expect(output['robots.txt']).toBeDefined();
|
||||
expect(output['generated.txt']).toBeDefined();
|
||||
},
|
||||
FOUR_MINUTES
|
||||
);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
const fs = require('fs');
|
||||
|
||||
// Adds a new file to the public folder at build time
|
||||
fs.writeFileSync('public/generated.txt', 'Generated');
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"scripts": {
|
||||
"now-build": "next build"
|
||||
"now-build": "node create-public-file.js && next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "8",
|
||||
"next": "9",
|
||||
"react": "16",
|
||||
"react-dom": "16"
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ const path = require('path');
|
||||
const {
|
||||
excludeFiles,
|
||||
validateEntrypoint,
|
||||
includeOnlyEntryDirectory,
|
||||
normalizePackageJson,
|
||||
getNextConfig
|
||||
getNextConfig,
|
||||
} = require('@now/next/dist/utils');
|
||||
const { FileRef } = require('@now/build-utils');
|
||||
|
||||
@@ -33,7 +32,7 @@ describe('excludeFiles', () => {
|
||||
const files = {
|
||||
'pages/index.js': new FileRef({ digest: 'index' }),
|
||||
'package.json': new FileRef({ digest: 'package' }),
|
||||
'package-lock.json': new FileRef({ digest: 'package-lock' })
|
||||
'package-lock.json': new FileRef({ digest: 'package-lock' }),
|
||||
};
|
||||
const result = excludeFiles(
|
||||
files,
|
||||
@@ -63,21 +62,6 @@ describe('validateEntrypoint', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('includeOnlyEntryDirectory', () => {
|
||||
it('should include files outside entry directory', () => {
|
||||
const entryDirectory = 'frontend';
|
||||
const files = {
|
||||
'frontend/pages/index.js': new FileRef({ digest: 'index' }),
|
||||
'package.json': new FileRef({ digest: 'package' }),
|
||||
'package-lock.json': new FileRef({ digest: 'package-lock' })
|
||||
};
|
||||
const result = includeOnlyEntryDirectory(files, entryDirectory);
|
||||
expect(result['frontend/pages/index.js']).toBeDefined();
|
||||
expect(result['package.json']).toBeUndefined();
|
||||
expect(result['package-lock.json']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizePackageJson', () => {
|
||||
it('should work without a package.json being supplied', () => {
|
||||
const result = normalizePackageJson();
|
||||
@@ -85,15 +69,15 @@ describe('normalizePackageJson', () => {
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas'
|
||||
}
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,29 +86,29 @@ describe('normalizePackageJson', () => {
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build': 'next build'
|
||||
}
|
||||
'now-build': 'next build',
|
||||
},
|
||||
};
|
||||
const result = normalizePackageJson(defaultPackage);
|
||||
expect(result).toEqual({
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas'
|
||||
}
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,23 +117,23 @@ describe('normalizePackageJson', () => {
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
next: 'latest'
|
||||
}
|
||||
next: 'latest',
|
||||
},
|
||||
};
|
||||
const result = normalizePackageJson(defaultPackage);
|
||||
expect(result).toEqual({
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas'
|
||||
}
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,23 +142,23 @@ describe('normalizePackageJson', () => {
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
next: 'latest'
|
||||
}
|
||||
next: 'latest',
|
||||
},
|
||||
};
|
||||
const result = normalizePackageJson(defaultPackage);
|
||||
expect(result).toEqual({
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas'
|
||||
}
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,23 +167,23 @@ describe('normalizePackageJson', () => {
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
next: 'latest'
|
||||
}
|
||||
next: 'latest',
|
||||
},
|
||||
};
|
||||
const result = normalizePackageJson(defaultPackage);
|
||||
expect(result).toEqual({
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: 'latest',
|
||||
'react-dom': 'latest'
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
next: 'v7.0.2-canary.49'
|
||||
next: 'v7.0.2-canary.49',
|
||||
},
|
||||
scripts: {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas'
|
||||
}
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -211,7 +195,7 @@ describe('normalizePackageJson', () => {
|
||||
dev: 'next',
|
||||
build: 'next build',
|
||||
start: 'next start',
|
||||
test: "xo && stylelint './pages/**/*.js' && jest"
|
||||
test: "xo && stylelint './pages/**/*.js' && jest",
|
||||
},
|
||||
main: 'pages/index.js',
|
||||
license: 'MIT',
|
||||
@@ -226,7 +210,7 @@ describe('normalizePackageJson', () => {
|
||||
'stylelint-config-recommended': '^2.1.0',
|
||||
'stylelint-config-styled-components': '^0.1.1',
|
||||
'stylelint-processor-styled-components': '^1.5.1',
|
||||
xo: '^0.23.0'
|
||||
xo: '^0.23.0',
|
||||
},
|
||||
dependencies: {
|
||||
consola: '^2.2.6',
|
||||
@@ -234,7 +218,7 @@ describe('normalizePackageJson', () => {
|
||||
next: '^7.0.2',
|
||||
react: '^16.6.3',
|
||||
'react-dom': '^16.6.3',
|
||||
'styled-components': '^4.1.1'
|
||||
'styled-components': '^4.1.1',
|
||||
},
|
||||
xo: {
|
||||
extends: 'xo-react',
|
||||
@@ -244,15 +228,15 @@ describe('normalizePackageJson', () => {
|
||||
'test',
|
||||
'pages/_document.js',
|
||||
'pages/index.js',
|
||||
'pages/home.js'
|
||||
'pages/home.js',
|
||||
],
|
||||
rules: {
|
||||
'react/no-unescaped-entities': null
|
||||
}
|
||||
'react/no-unescaped-entities': null,
|
||||
},
|
||||
},
|
||||
jest: {
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
testEnvironment: 'node',
|
||||
},
|
||||
};
|
||||
const result = normalizePackageJson(defaultPackage);
|
||||
expect(result).toEqual({
|
||||
@@ -263,7 +247,7 @@ describe('normalizePackageJson', () => {
|
||||
'now-build':
|
||||
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
|
||||
start: 'next start',
|
||||
test: "xo && stylelint './pages/**/*.js' && jest"
|
||||
test: "xo && stylelint './pages/**/*.js' && jest",
|
||||
},
|
||||
main: 'pages/index.js',
|
||||
license: 'MIT',
|
||||
@@ -283,12 +267,12 @@ describe('normalizePackageJson', () => {
|
||||
xo: '^0.23.0',
|
||||
consola: '^2.2.6',
|
||||
fontfaceobserver: '^2.0.13',
|
||||
'styled-components': '^4.1.1'
|
||||
'styled-components': '^4.1.1',
|
||||
},
|
||||
dependencies: {
|
||||
'next-server': 'v7.0.2-canary.49',
|
||||
react: '^16.6.3',
|
||||
'react-dom': '^16.6.3'
|
||||
'react-dom': '^16.6.3',
|
||||
},
|
||||
xo: {
|
||||
extends: 'xo-react',
|
||||
@@ -298,15 +282,15 @@ describe('normalizePackageJson', () => {
|
||||
'test',
|
||||
'pages/_document.js',
|
||||
'pages/index.js',
|
||||
'pages/home.js'
|
||||
'pages/home.js',
|
||||
],
|
||||
rules: {
|
||||
'react/no-unescaped-entities': null
|
||||
}
|
||||
'react/no-unescaped-entities': null,
|
||||
},
|
||||
},
|
||||
jest: {
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
testEnvironment: 'node',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,8 +52,9 @@ if 'handler' in __now_variables or 'Handler' in __now_variables:
|
||||
):
|
||||
body = base64.b64decode(body)
|
||||
|
||||
request_body = body.encode('utf-8') if isinstance(body, str) else body
|
||||
conn = http.client.HTTPConnection('0.0.0.0', port)
|
||||
conn.request(method, path, headers=headers, body=body)
|
||||
conn.request(method, path, headers=headers, body=request_body)
|
||||
res = conn.getresponse()
|
||||
data = res.read().decode('utf-8')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/python",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",
|
||||
|
||||
20
packages/now-python/test/fixtures/14-unicode-handler/index.py
vendored
Normal file
20
packages/now-python/test/fixtures/14-unicode-handler/index.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
import json
|
||||
|
||||
class handler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_POST(self):
|
||||
post_body = json.loads(self.rfile.read(int(self.headers['content-length'])).decode('utf-8'))
|
||||
name = post_body.get('name', 'someone')
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
response_data = {'greeting': f'hello, {name}'}
|
||||
self.wfile.write(json.dumps(response_data).encode('utf-8'))
|
||||
return
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write('ok'.encode('utf-8'))
|
||||
return
|
||||
19
packages/now-python/test/fixtures/14-unicode-handler/now.json
vendored
Normal file
19
packages/now-python/test/fixtures/14-unicode-handler/now.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "*.py",
|
||||
"use": "@now/python"
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"name": "Χριστοφορε"
|
||||
},
|
||||
"status": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
19
packages/now-python/test/fixtures/14-unicode-handler/probe.js
vendored
Normal file
19
packages/now-python/test/fixtures/14-unicode-handler/probe.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = async function({ deploymentUrl, fetch, randomness }) {
|
||||
const nowjson = require('./now.json');
|
||||
const probe = nowjson.probes[0];
|
||||
const probeUrl = `https://${deploymentUrl}${probe.path}`;
|
||||
const resp = await fetch(probeUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(probe.body),
|
||||
});
|
||||
|
||||
const text = await resp.text();
|
||||
const respBody = JSON.parse(text);
|
||||
|
||||
if (respBody.greeting !== 'hello, Χριστοφορε') {
|
||||
throw new Error(`unexpected response: ${respBody}`);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/static-build",
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build",
|
||||
@@ -19,11 +19,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cross-spawn": "6.0.0",
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/promise-timeout": "1.3.0",
|
||||
"cross-spawn": "6.0.5",
|
||||
"get-port": "5.0.0",
|
||||
"is-port-reachable": "2.0.1",
|
||||
"promise-timeout": "1.3.0",
|
||||
"ms": "2.1.2",
|
||||
"typescript": "3.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import ms from 'ms';
|
||||
import path from 'path';
|
||||
import spawn from 'cross-spawn';
|
||||
import getPort from 'get-port';
|
||||
import { timeout } from 'promise-timeout';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
|
||||
import { frameworks, Framework } from './frameworks';
|
||||
@@ -25,11 +26,20 @@ import {
|
||||
PrepareCacheOptions,
|
||||
} from '@now/build-utils';
|
||||
|
||||
async function checkForPort(port: number | undefined): Promise<void> {
|
||||
const sleep = (n: number) => new Promise(resolve => setTimeout(resolve, n));
|
||||
|
||||
const DEV_SERVER_PORT_BIND_TIMEOUT = ms('5m');
|
||||
|
||||
async function checkForPort(
|
||||
port: number | undefined,
|
||||
timeout: number
|
||||
): Promise<void> {
|
||||
const start = Date.now();
|
||||
while (!(await isPortReachable(port))) {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new Error(`Detecting port ${port} timed out after ${ms(timeout)}`);
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,32 +266,20 @@ export async function build({
|
||||
devPort = await getPort();
|
||||
nowDevScriptPorts.set(entrypoint, devPort);
|
||||
|
||||
const opts = {
|
||||
const opts: SpawnOptions = {
|
||||
cwd: entrypointDir,
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, PORT: String(devPort) },
|
||||
};
|
||||
|
||||
const child = spawn('yarn', ['run', devScript], opts);
|
||||
child.on('exit', () => nowDevScriptPorts.delete(entrypoint));
|
||||
if (child.stdout) {
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.pipe(process.stdout);
|
||||
}
|
||||
if (child.stderr) {
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
// Now wait for the server to have listened on `$PORT`, after which we
|
||||
// will ProxyPass any requests to that development server that come in
|
||||
// for this builder.
|
||||
try {
|
||||
await timeout(
|
||||
new Promise(resolve => {
|
||||
checkForPort(devPort).then(resolve);
|
||||
}),
|
||||
5 * 60 * 1000
|
||||
);
|
||||
await checkForPort(devPort, DEV_SERVER_PORT_BIND_TIMEOUT);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to detect a server running on port ${devPort}.\nDetails: https://err.sh/zeit/now/now-static-build-failed-to-detect-a-server`
|
||||
|
||||
15
packages/now-static-build/src/typings.d.ts
vendored
15
packages/now-static-build/src/typings.d.ts
vendored
@@ -1,7 +1,10 @@
|
||||
declare module 'is-port-reachable' {
|
||||
export interface IsPortReachableOptions {
|
||||
timeout?: number | undefined;
|
||||
host?: string;
|
||||
}
|
||||
export default function(port: number | undefined, options?: IsPortReachableOptions): Promise<boolean>;
|
||||
}
|
||||
export interface IsPortReachableOptions {
|
||||
timeout?: number | undefined;
|
||||
host?: string;
|
||||
}
|
||||
export default function(
|
||||
port: number | undefined,
|
||||
options?: IsPortReachableOptions
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -1671,6 +1671,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.30.tgz#f6c38b7ecbbf698b0bbd138315a0f0f18954f85f"
|
||||
integrity sha512-OftRLCgAzJP7vmKn9by/GVjnf4hloz/pXNOwPo0vKGAfXI7GqWXJi9N2kRar4cP5s1dGwuwcagWqO6iHBTq1Mg==
|
||||
|
||||
"@types/ms@0.7.31":
|
||||
version "0.7.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/multistream@2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/multistream/-/multistream-2.1.1.tgz#4badd2440ee3570594ea552420fe2e29ebe512bd"
|
||||
@@ -8793,11 +8798,6 @@ promise-retry@^1.1.1:
|
||||
err-code "^1.0.0"
|
||||
retry "^0.10.0"
|
||||
|
||||
promise-timeout@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/promise-timeout/-/promise-timeout-1.3.0.tgz#d1c78dd50a607d5f0a5207410252a3a0914e1014"
|
||||
integrity sha512-5yANTE0tmi5++POym6OgtFmwfDvOXABD9oj/jLQr5GPEyuNEb7jH4wbbANJceJid49jwhi1RddxnhnEAb/doqg==
|
||||
|
||||
promisepipe@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/promisepipe/-/promisepipe-3.0.0.tgz#c9b6e5aa861ef5fcce6134f6f75e14f8f30bd3b2"
|
||||
|
||||
Reference in New Issue
Block a user