Compare commits

..

68 Commits

Author SHA1 Message Date
Nathan Rajlich
0b89d30d6c Publish
- @now/build-utils@0.5.5
 - @now/cgi@0.1.4
 - @now/go@0.4.6
 - @now/mdx-deck@0.5.4
 - @now/next@0.3.2
 - @now/node-server@0.7.2
 - @now/node@0.7.2
 - @now/php@0.5.4
 - @now/python@0.2.2
 - @now/rust@0.2.4
2019-05-11 08:13:57 -07:00
Tim Neutkens
8a021c9417 [now-next] Add support for /api routes (#494) 2019-05-11 16:18:23 +02:00
Nathan Rajlich
f218771382 Publish
- @now/cgi@0.1.4-canary.0
 - @now/go@0.4.6-canary.0
 - @now/mdx-deck@0.5.4-canary.0
 - @now/node-server@0.7.2-canary.1
 - @now/node@0.7.2-canary.2
 - @now/php@0.5.4-canary.0
 - @now/python@0.2.2-canary.0
 - @now/rust@0.2.4-canary.1
2019-05-10 19:19:15 -07:00
Nathan Rajlich
17309291ed [now-node-server] Pass meta to download() function (#489) 2019-05-10 19:18:31 -07:00
Nathan Rajlich
86300577ae [now-rust] Pass meta to download() function 2019-05-10 17:14:22 -07:00
Nathan Rajlich
f9594e0d61 [now-python] Pass meta to download() function (#491) 2019-05-11 01:13:48 +02:00
Nathan Rajlich
20fd4b2e12 [now-php] Download files to workPath and pass meta (#490)
It's not clear to me why we were installing to `userfiles` directory,
so let me know if this breaks something.
2019-05-11 01:13:14 +02:00
Nathan Rajlich
718e4d0e0c [now-mdx-deck] Pass meta to download() function (#488) 2019-05-11 01:12:46 +02:00
Nathan Rajlich
dc3584cd08 [now-cgi] Download files to workPath and pass meta (#487) 2019-05-11 01:12:12 +02:00
Nathan Rajlich
b41788b241 Update yarn.lock 2019-05-10 13:17:05 -07:00
Nathan Rajlich
af9a2f9792 [now-node-server] Update @zeit/ncc to v0.18.5 2019-05-10 13:17:05 -07:00
Nathan Rajlich
f8b8e760de [now-node] Update @zeit/ncc to v0.18.5 2019-05-10 13:17:05 -07:00
Sophearak Tha
93d6ec8024 [now-go] Only use valid exported function with net/http interface (#477)
* Only use valid exported function with `net/http` interface

* Improve log, show link to docs if we coudn't parsed the entrypoint
2019-05-10 21:13:44 +07:00
Nathan Rajlich
7ed6b84056 Remove packages/now-rust/now-rust-0.2.3.tgz (#480)
It appears to have been accidentally committed in
bd2d05344e.
2019-05-08 14:50:57 -07:00
Nathan Rajlich
31da488365 Publish
- @now/build-utils@0.5.5-canary.1
 - @now/next@0.3.2-canary.1
 - @now/node@0.7.2-canary.1
2019-05-08 12:22:17 -07:00
Nathan Rajlich
8eaf05f782 Add initial CODEOWNERS file (#479)
* Add initial `CODEOWNERS` file

* Add `@now/go` to `CODEOWNERS` file
2019-05-08 12:07:32 -07:00
Nathan Rajlich
9311e90f27 [now-next] Fix failing unit test when isDev: true (#478) 2019-05-08 12:00:35 -07:00
Steven
c0de970de2 Add coverage to .gitignore (#473)
* Add coverage to `.gitignore`

* Add .tgz to `.gitignore`

* *.tgz
2019-05-07 17:57:09 -07:00
Nathan Rajlich
465ac2093d [now-node] Pass in the meta object to download() (#475)
Depends on https://github.com/zeit/now-builders/pull/474.
2019-05-07 17:56:39 -07:00
Nathan Rajlich
19ab0e8698 [now-build-utils] Make download() a no-op in now dev (#474)
This will be necessary for the update in `now dev` to have the builder
`workPath` be equal to the `cwd` source directory of the dev server.

Otherwise, unnecessary file modifications are made (copying the source
file to itself) and file corruption often occurs.
2019-05-07 17:56:16 -07:00
Steven
02fa98e5e3 Publish
- @now/build-utils@0.5.5-canary.0
 - @now/next@0.3.2-canary.0
 - @now/node-server@0.7.2-canary.0
 - @now/node@0.7.2-canary.0
 - @now/rust@0.2.4-canary.0
2019-05-07 17:08:55 -04:00
Steven
4aef9d48b0 [now-node] Bump ncc to 0.18.3 (#472) 2019-05-07 15:40:14 -04:00
Luis Fernando Alvarez D
bd2d05344e [now-next] Add public files to the output (#468) 2019-05-07 14:06:05 -05:00
Steven
edc7696623 Add newline to .gitignore 2019-05-07 09:28:59 -04:00
Steven
e2f91094bc Publish
- @now/bash@0.2.3
 - @now/build-utils@0.5.4
 - @now/cgi@0.1.3
 - @now/go@0.4.5
 - @now/html-minifier@1.1.3
 - @now/lambda@0.5.3
 - @now/md@0.5.3
 - @now/mdx-deck@0.5.3
 - @now/next@0.3.1
 - @now/node-bridge@1.1.2
 - @now/node-server@0.7.1
 - @now/node@0.7.1
 - @now/optipng@0.6.1
 - @now/php-bridge@0.5.2
 - @now/php@0.5.3
 - @now/python@0.2.1
 - @now/rust@0.2.3
 - @now/static-build@0.5.5
 - @now/wordpress@0.5.2
2019-05-07 07:09:50 -04:00
Steven
38dba57378 Bump stable version 2019-05-07 07:00:57 -04:00
Nathan Rajlich
be6a6ba1d7 Publish
- @now/build-utils@0.5.2-canary.2
 - @now/next@0.2.1-canary.1
2019-05-06 17:07:12 -07:00
Nathan Rajlich
31fb5d9ec8 [now-next] Sync runtime env vars in dev server after app.prepare() (#467)
* [now-next] Sync runtime env vars in dev server after `app.prepare()`

* Update packages/now-next/src/index.ts

Co-Authored-By: TooTallNate <n@n8.io>

* Update packages/now-next/src/index.ts

Co-Authored-By: TooTallNate <n@n8.io>

* Add `syncEnvVars()` helper function for common logic
2019-05-06 16:43:20 -07:00
Joe Haddad
6c8f946a48 [now-build-utils] Remove mutable option and add sha+ephemeral scheme (#466)
Co-Authored-By: Steven <steven@ceriously.com>

* Add an ephemeral option for files

* Use array destructuring

Co-Authored-By: Timer <joe.haddad@zeit.co>

* Remove mutable option

* Remove code all together

* Introduce `sha+ephemeral` handling

* http => https

* Elaborate more on the cloudfront url

* Update comment a bit more

* Add comment explaining other url
2019-05-06 19:12:39 -04:00
Steven
d59e1b9789 Publish
- @now/build-utils@0.5.2-canary.1
 - @now/go@0.4.3-canary.1
2019-05-06 08:47:35 -04:00
Steven
2852d3fbc3 [now-build-utils] Add yarn --ignore-engines during install (#463) 2019-05-05 22:57:37 -04:00
Sophearak Tha
d0292eb751 Improve go checking in user dev machine (#460) 2019-05-06 09:11:24 +07:00
Steven
17bbf69346 Publish
- @now/node-server@0.6.1-canary.2
 - @now/python@0.1.1-canary.2
2019-05-03 19:48:26 -04:00
Steven
4fb4229c90 Remove python/pip installer (#459)
* Remove python/pip installer

* Add back PYTHONUSERBASE env var
2019-05-03 19:46:47 -04:00
Steven
03b7586b50 [now-node-server] Fix unit tests 2019-05-03 14:09:45 -04:00
Mickaël Allonneau
a1427866ca [now-node-server] 'config.includeFiles' accepts a string (#442)
* 1st try (fails)

* 2nd try (fails)

* 3rd try (fails)

* 4th try (fail)
2019-05-03 13:54:33 -04:00
Nathan Rajlich
5f787b8146 Publish
- @now/bash@0.2.1-canary.0
 - @now/cgi@0.1.1-canary.0
 - @now/html-minifier@1.1.1-canary.0
 - @now/lambda@0.5.1-canary.0
 - @now/md@0.5.1-canary.0
 - @now/node-server@0.6.1-canary.1
 - @now/node@0.6.1-canary.2
 - @now/optipng@0.5.1-canary.0
 - @now/php@0.5.1-canary.0
 - @now/python@0.1.1-canary.1
 - @now/rust@0.2.1-canary.0
2019-05-02 15:21:06 -07:00
Nathan Rajlich
b03405a665 Add default shouldServe() to appropriate builders (#456)
This adds the default `shouldServe()` implementation to the following
builders:

 * `@now/bash`
 * `@now/cgi`
 * `@now/html-minifier`
 * `@now/lambda`
 * `@now/md`
 * `@now/node-server`
 * `@now/node`
 * `@now/optipng`
 * `@now/php`
 * `@now/python`
 * `@now/rust`

The default implementation may be used for these builders because
they map 1-1 with their entrypoint file -> output file/lambda.
2019-05-02 15:09:58 -07:00
Steven
4393dad15a Publish
- @now/node@0.6.1-canary.1
 - @now/static-build@0.5.3-canary.1
2019-05-02 16:07:38 -04:00
Marios Antonoudiou
b4d604b2e9 [now-static-build] Fix @now/static-build nested build (#452) 2019-05-02 15:15:40 -04:00
Mickaël Allonneau
5fb6e5c0ba [now-node] 'config.includeFiles' accepts a string (#441) 2019-05-02 14:26:35 -04:00
Steven
9d7dd3a713 Publish
- @now/python@0.1.1-canary.0
2019-05-02 11:32:59 -04:00
Steven
4f867b320d [now-python] Upgrade build env to python3.6 (#450)
* [now-python] Upgarde build env to python3.6

* Add support for isDev
2019-05-02 11:00:39 -04:00
Nathan Rajlich
c153690104 Publish
- @now/build-utils@0.5.2-canary.0
 - @now/go@0.4.3-canary.0
 - @now/next@0.2.1-canary.0
 - @now/static-build@0.5.3-canary.0
2019-05-01 23:34:18 -07:00
Nathan Rajlich
8c1b96edf7 [now-build-utils] Use cross-spawn for Windows support (#449) 2019-05-01 23:26:20 -07:00
Carlos Alexandro Becker
15c83a69f7 [now-go] Add support for config.includeFiles (#447) 2019-05-01 17:39:53 -04:00
Steven
0986de85ee [now-static-build] Warn when now-dev script is missing (#443)
* Warn when now-dev script is missing

* Add link to local dev docs

* Update packages/now-static-build/index.js

Co-Authored-By: styfle <steven@ceriously.com>

* Fix url
2019-05-01 17:32:50 -04:00
Leo Lamprecht
94c5d83ccc Copy the entire process environment to child (#448)
* Copy the entire process environment to child

* Removed lockfile

* Removed useless code

* Add comment
2019-05-01 22:26:35 +02:00
Steven
ff49b9d32d Publish
- @now/node-server@0.6.1-canary.0
 - @now/node@0.6.1-canary.0
2019-05-01 14:20:43 -04:00
Steven
ec5290dab1 [node-node] Bump ncc to 0.18.2 (#446) 2019-05-01 12:19:43 -04:00
Steven
4f758ec84e Create CODE_OF_CONDUCT.md (#444) 2019-05-01 09:04:34 -04:00
Steven
7951be156a Publish
- @now/build-utils@0.5.1
 - @now/go@0.4.2
 - @now/static-build@0.5.2
2019-04-30 10:33:52 -04:00
Nathan Rajlich
1bafc1d7b7 Publish
- @now/go@0.4.1
2019-04-29 18:03:38 -07:00
Sophearak Tha
1493101325 [now-go] Improve speed for now dev (#438)
* Improve speed for build() `@now/go` `now dev`

* Remove duplicate line

* Improve code flow

* Improve logging information

* no need to download Go if it available
2019-04-29 18:01:09 -07:00
Nathan Rajlich
824b044a96 Publish
- @now/static-build@0.5.1
2019-04-29 14:57:30 -07:00
Nathan Rajlich
0978be4c3d Publish
- @now/static-build@0.5.1-canary.0
2019-04-29 14:00:45 -07:00
Nathan Rajlich
dc832aa6c3 Regenerate yarn.lock file 2019-04-29 14:00:26 -07:00
Nathan Rajlich
8df77fe4fa [now-build-utils] Wait for the dev server to print the URL with correct port number (#439)
This is an alternative approach to detecting when the dev server is
ready to receive HTTP traffic, also bumps the timeout to 5 minutes.
2019-04-29 13:58:44 -07:00
Sophearak Tha
ff413b45fa Publish
- @now/go@0.4.1-canary.3
2019-04-29 16:46:58 +07:00
Sophearak Tha
e7befb5dc1 Make install @now/go faster (#435) 2019-04-29 16:46:08 +07:00
Steven
b898f82771 [now-go] Use optimized build flags (#428)
* Add optimized build output

* Add `config.ldsflags`
2019-04-29 09:41:50 +07:00
Leo Lamprecht
e6b22cb0df Publish
- @now/go@0.4.1-canary.2
2019-04-28 19:54:24 +00:00
Sophearak Tha
cbfe4a133d Using full path for checking against entrypoint (#433) 2019-04-29 00:44:31 +07:00
Sophearak Tha
823b78c626 Publish
- @now/go@0.4.1-canary.1
2019-04-28 21:22:30 +07:00
Sophearak Tha
03e1255043 Improve rebuild speed for now dev (#432) 2019-04-28 20:56:07 +07:00
Steven
3373cbca4e Publish
- @now/build-utils@0.5.1-canary.0
 - @now/go@0.4.1-canary.0
2019-04-25 17:11:31 -04:00
Sophearak Tha
4fba4b5f67 [now-go] Migrated to TypeScript and support Builder v2 API (#412)
* Migrated `@now/go` to TypeScript

* Add support for Builder v2 API

* Add now-go to .eslintignore

* Update go bin path

* Update packages/now-go/.gitignore

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Update packages/now-go/go-helpers.ts

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Update packages/now-go/go-helpers.ts

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Using American English for consistency

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Rename analyse.go to analyze.go

* Update packages/now-go/go-helpers.ts

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Remove `mkdirp-promise` from now-go

* Ensure `watch` directory-aware given `entrypoint` in subdirectory

* Support export struct type declaration

* Improve log

* Add type to `analyzed`

* Update packages/now-go/index.ts

Co-Authored-By: sophearak <t.sophearak@gmail.com>

* Migrate test fixtures from `add/now-go-tests`
2019-04-25 17:07:56 -04:00
Steven
9fcf6da3c1 Add comment to fsPath about absolute path (#427) 2019-04-25 11:27:36 -04:00
89 changed files with 1401 additions and 554 deletions

View File

@@ -8,3 +8,4 @@
/packages/now-node-bridge/*
/packages/now-python/*
/packages/now-optipng/dist/*
/packages/now-go/*

7
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,7 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
* @styfle
/packages/now-node @styfle @tootallnate
/packages/now-next @styfle @dav-is
/packages/now-go @styfle @sophearak

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
node_modules
tmp
target/
.next
.next
coverage
*.tgz

74
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,74 @@
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [abuse@zeit.co](mailto:abuse@zeit.co). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,10 +1,13 @@
const execa = require('execa');
const { join } = require('path');
const snakeCase = require('snake-case');
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
const {
glob,
download,
createLambda,
getWriteableDirectory,
shouldServe,
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.config = {
maxLambdaSize: '10mb',
@@ -15,7 +18,7 @@ exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.build = async ({
workPath, files, entrypoint, config,
}) => {
const srcDir = await getWritableDirectory();
const srcDir = await getWriteableDirectory();
console.log('downloading files...');
await download(files, srcDir);
@@ -55,3 +58,5 @@ exports.build = async ({
[entrypoint]: lambda,
};
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/bash",
"version": "0.2.0",
"version": "0.2.3",
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
"main": "index.js",
"author": "Nathan Rajlich <nate@zeit.co>",

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "0.5.0",
"version": "0.5.5",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -10,8 +10,10 @@
"directory": "packages/now-build-utils"
},
"dependencies": {
"@types/cross-spawn": "6.0.0",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "7.0.0",
"glob": "7.1.3",

View File

@@ -8,7 +8,6 @@ import { File } from './types';
interface FileRefOptions {
mode?: number;
digest: string;
mutable?: boolean;
}
const semaToDownloadFromS3 = new Sema(5);
@@ -26,26 +25,29 @@ export default class FileRef implements File {
public type: 'FileRef';
public mode: number;
public digest: string;
public mutable: boolean;
constructor({ mode = 0o100644, digest, mutable = false }: FileRefOptions) {
constructor({ mode = 0o100644, digest }: FileRefOptions) {
assert(typeof mode === 'number');
assert(typeof digest === 'string');
assert(typeof mutable === 'boolean');
this.type = 'FileRef';
this.mode = mode;
this.digest = digest;
this.mutable = mutable;
}
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
let url = '';
// sha:24be087eef9fac01d61b30a725c1a10d7b45a256
const digestParts = this.digest.split(':');
if (digestParts[0] === 'sha') {
url = this.mutable
? `https://s3.amazonaws.com/now-files/${digestParts[1]}`
: `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`;
const [digestType, digestHash] = this.digest.split(':');
if (digestType === 'sha') {
// This CloudFront URL edge caches the `now-files` S3 bucket to prevent
// overloading it
// `https://now-files.s3.amazonaws.com/${digestHash}`
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestHash}`;
} else if (digestType === 'sha+ephemeral') {
// This URL is currently only used for cache files that constantly
// change. We shouldn't cache it on CloudFront because it'd always be a
// MISS.
url = `https://now-ephemeral-files.s3.amazonaws.com/${digestHash}`;
} else {
throw new Error('Expected digest to be sha');
}
@@ -58,14 +60,14 @@ export default class FileRef implements File {
const resp = await fetch(url);
if (!resp.ok) {
const error = new BailableError(
`download: ${resp.status} ${resp.statusText} for ${url}`,
`download: ${resp.status} ${resp.statusText} for ${url}`
);
if (resp.status === 403) error.bail = true;
throw error;
}
return resp.body;
},
{ factor: 1, retries: 3 },
{ factor: 1, retries: 3 }
);
} finally {
// console.timeEnd(`downloading ${url}`);
@@ -77,15 +79,15 @@ export default class FileRef implements File {
let flag = false;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
return multiStream(cb => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
.then(stream => {
cb(null, stream);
})
.catch((error) => {
.catch(error => {
cb(error, null);
});
});

View File

@@ -4,11 +4,11 @@ import { File, Files, Meta } from '../types';
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
export interface DownloadedFiles {
[filePath: string]: FileFsRef
[filePath: string]: FileFsRef;
}
const S_IFMT = 61440; /* 0170000 type of file */
const S_IFLNK = 40960; /* 0120000 symbolic link */
const S_IFMT = 61440; /* 0170000 type of file */
const S_IFLNK = 40960; /* 0120000 symbolic link */
export function isSymbolicLink(mode: number): boolean {
return (mode & S_IFMT) === S_IFLNK;
@@ -17,9 +17,9 @@ export function isSymbolicLink(mode: number): boolean {
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
if (mode && isSymbolicLink(mode) && file.type === 'FileFsRef') {
const [ target ] = await Promise.all([
const [target] = await Promise.all([
readlink((file as FileFsRef).fsPath),
mkdirp(path.dirname(fsPath))
mkdirp(path.dirname(fsPath)),
]);
await symlink(target, fsPath);
return FileFsRef.fromFsPath({ mode, fsPath });
@@ -34,12 +34,25 @@ async function removeFile(basePath: string, fileMatched: string) {
await remove(file);
}
export default async function download(files: Files, basePath: string, meta?: Meta): Promise<DownloadedFiles> {
export default async function download(
files: Files,
basePath: string,
meta?: Meta
): Promise<DownloadedFiles> {
const { isDev = false, filesChanged = null, filesRemoved = null } =
meta || {};
if (isDev) {
// In `now dev`, the `download()` function is a no-op because
// the `basePath` matches the `cwd` of the dev server, so the
// source files are already available.
return files as DownloadedFiles;
}
const files2: DownloadedFiles = {};
const { filesChanged = null, filesRemoved = null } = meta || {};
await Promise.all(
Object.keys(files).map(async (name) => {
Object.keys(files).map(async name => {
// If the file does not exist anymore, remove it.
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
await removeFile(basePath, name);
@@ -55,7 +68,7 @@ export default async function download(files: Files, basePath: string, meta?: Me
const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
})
);
return files2;

View File

@@ -8,12 +8,16 @@ import FileFsRef from '../file-fs-ref';
type GlobOptions = vanillaGlob_.IOptions;
interface FsFiles {
[filePath: string]: FileFsRef
[filePath: string]: FileFsRef;
}
const vanillaGlob = promisify(vanillaGlob_);
export default async function glob(pattern: string, opts: GlobOptions | string, mountpoint?: string): Promise<FsFiles> {
export default async function glob(
pattern: string,
opts: GlobOptions | string,
mountpoint?: string
): Promise<FsFiles> {
let options: GlobOptions;
if (typeof opts === 'string') {
options = { cwd: opts };
@@ -23,7 +27,7 @@ export default async function glob(pattern: string, opts: GlobOptions | string,
if (!options.cwd) {
throw new Error(
'Second argument (basePath) must be specified for names of resulting files',
'Second argument (basePath) must be specified for names of resulting files'
);
}
@@ -41,11 +45,11 @@ export default async function glob(pattern: string, opts: GlobOptions | string,
const files = await vanillaGlob(pattern, options);
for (const relativePath of files) {
const fsPath = path.join(options.cwd!, relativePath);
const fsPath = path.join(options.cwd!, relativePath).replace(/\\/g, '/');
let stat: Stats = options.statCache![fsPath] as Stats;
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
);
if (stat.isFile()) {
const isSymlink = options.symlinks![fsPath];

View File

@@ -1,22 +1,28 @@
import assert from 'assert';
import fs from 'fs-extra';
import path from 'path';
import { spawn, SpawnOptions } from 'child_process';
import spawn from 'cross-spawn';
import { SpawnOptions } from 'child_process';
function spawnAsync(command: string, args: string[], cwd: string, opts: SpawnOptions = {}) {
function spawnAsync(
command: string,
args: string[],
cwd: string,
opts: SpawnOptions = {}
) {
return new Promise<void>((resolve, reject) => {
const stderrLogs: Buffer[] = []
const stderrLogs: Buffer[] = [];
opts = { stdio: 'inherit', cwd, ...opts };
const child = spawn(command, args, opts);
if (opts.stdio === 'pipe'){
if (opts.stdio === 'pipe') {
child.stderr.on('data', data => stderrLogs.push(data));
}
child.on('error', reject);
child.on('close', (code, signal) => {
if (code === 0) {
return resolve()
return resolve();
}
const errorLogs = stderrLogs.map(line => line.toString()).join('');
@@ -58,13 +64,15 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
);
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName],
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
);
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.pathExists(
path.join(currentDestPath, 'package-lock.json'),
path.join(currentDestPath, 'package-lock.json')
);
break;
}
@@ -77,7 +85,10 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
return { hasScript, hasPackageLockJson };
}
export async function installDependencies(destPath: string, args: string[] = []) {
export async function installDependencies(
destPath: string,
args: string[] = []
) {
assert(path.isAbsolute(destPath));
let commandArgs = args;
@@ -91,7 +102,7 @@ export async function installDependencies(destPath: string, args: string[] = [])
// Node.js version that `@now/node` and `@now/node-server` use
npm_config_target: '8.10.0',
},
stdio: 'pipe'
stdio: 'pipe',
};
if (hasPackageLockJson) {
@@ -105,9 +116,9 @@ export async function installDependencies(destPath: string, args: string[] = [])
} else {
await spawnAsync(
'yarn',
['--cwd', destPath].concat(commandArgs),
['--ignore-engines', '--cwd', destPath].concat(commandArgs),
destPath,
opts as SpawnOptions,
opts as SpawnOptions
);
}
}
@@ -120,7 +131,7 @@ export async function runPackageJsonScript(
assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs(
destPath,
scriptName,
scriptName
);
if (!hasScript) return false;
@@ -129,7 +140,12 @@ export async function runPackageJsonScript(
await spawnAsync('npm', ['run', scriptName], destPath, opts);
} else {
console.log(`running "yarn run ${scriptName}"`);
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], destPath, opts);
await spawnAsync(
'yarn',
['--cwd', destPath, 'run', scriptName],
destPath,
opts
);
}
return true;

View File

@@ -5,6 +5,9 @@ export interface File {
type: string;
mode: number;
toStream: () => NodeJS.ReadableStream;
/**
* The absolute path to the file in the filesystem
*/
fsPath?: string;
}

View File

@@ -0,0 +1,9 @@
const scheduler = require('@google-cloud/scheduler');
module.exports = (_, res) => {
if (scheduler) {
res.end('found:RANDOMNESS_PLACEHOLDER');
} else {
res.end('nope:RANDOMNESS_PLACEHOLDER');
}
};

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"builds": [
{ "src": "index.js", "use": "@now/node", "config": { "maxLambdaSize": "15mb" } }
],
"probes": [
{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -0,0 +1,8 @@
{
"name": "15-yarn-ignore-engines",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@google-cloud/scheduler": "0.3.0"
}
}

View File

@@ -5,15 +5,17 @@ const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.build = async ({ files, entrypoint }) => {
exports.build = async ({
workPath, files, entrypoint, meta,
}) => {
console.log('downloading files...');
const srcDir = await getWritableDirectory();
const outDir = await getWritableDirectory();
await download(files, srcDir);
await download(files, workPath, meta);
const handlerPath = path.join(__dirname, 'handler');
await copyFile(handlerPath, path.join(outDir, 'handler'));
@@ -23,7 +25,7 @@ exports.build = async ({ files, entrypoint }) => {
// For now only the entrypoint file is copied into the lambda
await copyFile(
path.join(srcDir, entrypoint),
path.join(workPath, entrypoint),
path.join(outDir, entrypoint),
);
@@ -40,3 +42,5 @@ exports.build = async ({ files, entrypoint }) => {
[entrypoint]: lambda,
};
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/cgi",
"version": "0.1.0",
"version": "0.1.4",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,5 +1,6 @@
node_modules
*.log
/?.js
/go
/get-exported-function-name
/analyze
*.js
!util/install.js

View File

@@ -0,0 +1,5 @@
*.ts
test
tsconfig.json
package-lock.json
yarn.lock

View File

@@ -1,129 +0,0 @@
const tar = require('tar');
const execa = require('execa');
const fetch = require('node-fetch');
const { mkdirp } = require('fs-extra');
const { dirname, join } = require('path');
const debug = require('debug')('@now/go:go-helpers');
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
const platformMap = new Map([['win32', 'windows']]);
// Location where the `go` binary will be installed after `postinstall`
const GO_DIR = join(__dirname, 'go');
const GO_BIN = join(GO_DIR, 'bin/go');
const getPlatform = p => platformMap.get(p) || p;
const getArch = a => archMap.get(a) || a;
const getGoUrl = (version, platform, arch) => {
const goArch = getArch(arch);
const goPlatform = getPlatform(platform);
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
};
async function getExportedFunctionName(filePath) {
debug('Detecting handler name for %o', filePath);
const bin = join(__dirname, 'get-exported-function-name');
const args = [filePath];
const name = await execa.stdout(bin, args);
debug('Detected exported name %o', name);
return name;
}
// Creates a `$GOPATH` directory tree, as per `go help gopath` instructions.
// Without this, `go` won't recognize the `$GOPATH`.
function createGoPathTree(goPath, platform, arch) {
const tuple = `${getPlatform(platform)}_${getArch(arch)}`;
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
return Promise.all([
mkdirp(join(goPath, 'bin')),
mkdirp(join(goPath, 'pkg', tuple)),
]);
}
async function get({ src } = {}) {
const args = ['get'];
if (src) {
debug('Fetching `go` dependencies for file %o', src);
args.push(src);
} else {
debug('Fetching `go` dependencies for cwd %o', this.cwd);
}
await this(...args);
}
async function build({ src, dest }) {
debug('Building `go` binary %o -> %o', src, dest);
let sources;
if (Array.isArray(src)) {
sources = src;
} else {
sources = [src];
}
await this('build', '-o', dest, ...sources);
}
async function createGo(
goPath,
platform = process.platform,
arch = process.arch,
opts = {},
goMod = false,
) {
const env = {
...process.env,
PATH: `${dirname(GO_BIN)}:${process.env.PATH}`,
GOPATH: goPath,
...opts.env,
};
if (goMod) {
env.GO111MODULE = 'on';
}
function go(...args) {
debug('Exec %o', `go ${args.join(' ')}`);
return execa('go', args, { stdio: 'inherit', ...opts, env });
}
go.cwd = opts.cwd || process.cwd();
go.get = get;
go.build = build;
go.goPath = goPath;
await createGoPathTree(goPath, platform, arch);
return go;
}
async function downloadGo(
dir = GO_DIR,
version = '1.12',
platform = process.platform,
arch = process.arch,
) {
debug('Installing `go` v%s to %o for %s %s', version, dir, platform, arch);
const url = getGoUrl(version, platform, arch);
debug('Downloading `go` URL: %o', url);
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
}
// TODO: use a zip extractor when `ext === "zip"`
await mkdirp(dir);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dir, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
return createGo(dir, platform, arch);
}
module.exports = {
createGo,
downloadGo,
getExportedFunctionName,
};

View File

@@ -0,0 +1,161 @@
import tar from 'tar';
import execa from 'execa';
import fetch from 'node-fetch';
import { mkdirp, pathExists } from 'fs-extra';
import { dirname, join } from 'path';
import { homedir } from 'os';
import Debug from 'debug';
const debug = Debug('@now/go:go-helpers');
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
const platformMap = new Map([['win32', 'windows']]);
// Location where the `go` binary will be installed after `postinstall`
const GO_DIR = join(__dirname, 'go');
const GO_BIN = join(GO_DIR, 'bin/go');
const getPlatform = (p: string) => platformMap.get(p) || p;
const getArch = (a: string) => archMap.get(a) || a;
const getGoUrl = (version: string, platform: string, arch: string) => {
const goArch = getArch(arch);
const goPlatform = getPlatform(platform);
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
};
export async function getAnalyzedEntrypoint(filePath: string) {
debug('Analyzing entrypoint %o', filePath);
const bin = join(__dirname, 'analyze');
const isAnalyzeExist = await pathExists(bin);
if (!isAnalyzeExist) {
const src = join(__dirname, 'util', 'analyze.go');
const dest = join(__dirname, 'analyze');
const go = await downloadGo();
await go.build(src, dest);
}
const args = [filePath];
const analyzed = await execa.stdout(bin, args);
debug('Analyzed entrypoint %o', analyzed);
return analyzed;
}
// Creates a `$GOPATH` directory tree, as per `go help gopath` instructions.
// Without this, `go` won't recognize the `$GOPATH`.
function createGoPathTree(goPath: string, platform: string, arch: string) {
const tuple = `${getPlatform(platform)}_${getArch(arch)}`;
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
return Promise.all([
mkdirp(join(goPath, 'bin')),
mkdirp(join(goPath, 'pkg', tuple)),
]);
}
class GoWrapper {
private env: { [key: string]: string };
private opts: execa.Options;
constructor(env: { [key: string]: string }, opts: execa.Options = {}) {
if (!opts.cwd) {
opts.cwd = process.cwd();
}
this.env = env;
this.opts = opts;
}
private execute(...args: string[]) {
const { opts, env } = this;
debug('Exec %o', `go ${args.join(' ')}`);
return execa('go', args, { stdio: 'inherit', ...opts, env });
}
mod() {
return this.execute('mod', 'tidy');
}
get(src?: string) {
const args = ['get'];
if (src) {
debug('Fetching `go` dependencies for file %o', src);
args.push(src);
} else {
debug('Fetching `go` dependencies for cwd %o', this.opts.cwd);
}
return this.execute(...args);
}
build(src: string | string[], dest: string, ldsflags = '-s -w') {
debug('Building optimized `go` binary %o -> %o', src, dest);
const sources = Array.isArray(src) ? src : [src];
return this.execute('build', '-ldflags', ldsflags, '-o', dest, ...sources);
}
}
export async function createGo(
goPath: string,
platform = process.platform,
arch = process.arch,
opts: execa.Options = {},
goMod = false
) {
const path = `${dirname(GO_BIN)}:${process.env.PATH}`;
const env: { [key: string]: string } = {
...process.env,
PATH: path,
GOPATH: goPath,
...opts.env,
};
if (goMod) {
env.GO111MODULE = 'on';
}
await createGoPathTree(goPath, platform, arch);
return new GoWrapper(env, opts);
}
export async function downloadGo(
dir = GO_DIR,
version = '1.12',
platform = process.platform,
arch = process.arch
) {
// Check default `Go` in user machine
const isUserGo = await pathExists(join(homedir(), 'go'));
// If we found GOPATH in ENV, or default `Go` path exists
// asssume that user have `Go` installed
if (isUserGo || process.env.GOPATH !== undefined) {
return createGo(dir, platform, arch);
} else {
// Check `Go` bin in builder CWD
const isGoExist = await pathExists(join(dir, 'bin'));
if (!isGoExist) {
debug(
'Installing `go` v%s to %o for %s %s',
version,
dir,
platform,
arch
);
const url = getGoUrl(version, platform, arch);
debug('Downloading `go` URL: %o', url);
console.log('Downloading Go ...');
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
}
// TODO: use a zip extractor when `ext === "zip"`
await mkdirp(dir);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dir, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
}
return createGo(dir, platform, arch);
}
}

View File

@@ -1,52 +1,100 @@
const { join, sep, dirname } = require('path');
const {
readFile, writeFile, pathExists, move,
} = require('fs-extra');
import { join, sep, dirname } from 'path';
import { readFile, writeFile, pathExists, move } from 'fs-extra';
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download.js'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory.js'); // eslint-disable-line import/no-extraneous-dependencies
const { createGo, getExportedFunctionName } = require('./go-helpers');
import {
glob,
download,
createLambda,
getWriteableDirectory,
BuildOptions,
shouldServe,
Files,
} from '@now/build-utils';
const config = {
import { createGo, getAnalyzedEntrypoint } from './go-helpers';
interface Analyzed {
found?: boolean;
packageName: string;
functionName: string;
watch: string[];
}
interface BuildParamsMeta {
isDev: boolean | undefined;
}
interface BuildParamsType extends BuildOptions {
files: Files;
entrypoint: string;
workPath: string;
meta: BuildParamsMeta;
}
export const version = 2;
export const config = {
maxLambdaSize: '10mb',
};
async function build({ files, entrypoint }) {
export async function build({
files,
entrypoint,
config,
meta = {} as BuildParamsMeta,
}: BuildParamsType) {
console.log('Downloading user files...');
const entrypointArr = entrypoint.split(sep);
const [goPath, outDir] = await Promise.all([
getWritableDirectory(),
getWritableDirectory(),
let [goPath, outDir] = await Promise.all([
getWriteableDirectory(),
getWriteableDirectory(),
]);
if (meta.isDev) {
const devGoPath = `dev${entrypointArr[entrypointArr.length - 1]}`;
const goPathArr = goPath.split(sep);
goPathArr.pop();
goPathArr.push(devGoPath);
goPath = goPathArr.join(sep);
}
const srcPath = join(goPath, 'src', 'lambda');
const downloadedFiles = await download(files, srcPath);
const input = dirname(downloadedFiles[entrypoint].fsPath);
var includedFiles: Files = {};
if (config && config.includeFiles) {
for (const pattern of config.includeFiles) {
const files = await glob(pattern, input);
for (const assetName of Object.keys(files)) {
includedFiles[assetName] = files[assetName];
}
}
}
console.log(`Parsing AST for "${entrypoint}"`);
let parseFunctionName;
let analyzed: string;
try {
parseFunctionName = await getExportedFunctionName(
downloadedFiles[entrypoint].fsPath,
);
analyzed = await getAnalyzedEntrypoint(downloadedFiles[entrypoint].fsPath);
} catch (err) {
console.log(`Failed to parse AST for "${entrypoint}"`);
throw err;
}
if (!parseFunctionName) {
if (!analyzed) {
const err = new Error(
`Could not find an exported function in "${entrypoint}"`,
`Could not find an exported function in "${entrypoint}"
Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#entrypoint
`
);
console.log(err.message);
throw err;
}
const handlerFunctionName = parseFunctionName.split(',')[0];
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
const handlerFunctionName = parsedAnalyzed.functionName;
console.log(
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`,
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
);
// we need `main.go` in the same dir as the entrypoint,
@@ -54,7 +102,7 @@ async function build({ files, entrypoint }) {
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
// check if package name other than main
const packageName = parseFunctionName.split(',')[1];
const packageName = parsedAnalyzed.packageName;
const isGoModExist = await pathExists(join(entrypointDirname, 'go.mod'));
if (packageName !== 'main') {
const go = await createGo(
@@ -64,7 +112,7 @@ async function build({ files, entrypoint }) {
{
cwd: entrypointDirname,
},
true,
true
);
if (!isGoModExist) {
try {
@@ -80,7 +128,7 @@ async function build({ files, entrypoint }) {
const mainModGoFileName = 'main__mod__.go';
const modMainGoContents = await readFile(
join(__dirname, mainModGoFileName),
'utf8',
'utf8'
);
let goPackageName = `${packageName}/${packageName}`;
@@ -89,11 +137,10 @@ async function build({ files, entrypoint }) {
if (isGoModExist) {
const goModContents = await readFile(
join(entrypointDirname, 'go.mod'),
'utf8',
'utf8'
);
goPackageName = `${
goModContents.split('\n')[0].split(' ')[1]
}/${packageName}`;
const usrModName = goModContents.split('\n')[0].split(' ')[1];
goPackageName = `${usrModName}/${packageName}`;
}
const mainModGoContents = modMainGoContents
@@ -103,34 +150,56 @@ async function build({ files, entrypoint }) {
// write main__mod__.go
await writeFile(
join(entrypointDirname, mainModGoFileName),
mainModGoContents,
mainModGoContents
);
// move user go file to folder
try {
// default path
let finalDestination = join(entrypointDirname, packageName, entrypoint);
const entrypointArr = entrypoint.split(sep);
let forceMove = false;
if (meta.isDev) {
forceMove = true;
}
// if `entrypoint` include folder, only use filename
if (entrypointArr.length > 1) {
finalDestination = join(
entrypointDirname,
packageName,
entrypointArr.pop(),
entrypointArr[entrypointArr.length - 1]
);
}
await move(downloadedFiles[entrypoint].fsPath, finalDestination);
await move(downloadedFiles[entrypoint].fsPath, finalDestination, {
overwrite: forceMove,
});
} catch (err) {
console.log('failed to move entry to package folder');
throw err;
}
console.log('tidy go.mod file');
if (meta.isDev) {
const isGoModBk = await pathExists(join(entrypointDirname, 'go.mod.bk'));
if (isGoModBk) {
await move(
join(entrypointDirname, 'go.mod.bk'),
join(entrypointDirname, 'go.mod'),
{ overwrite: true }
);
await move(
join(entrypointDirname, 'go.sum.bk'),
join(entrypointDirname, 'go.sum'),
{ overwrite: true }
);
}
}
console.log('Tidy `go.mod` file...');
try {
// ensure go.mod up-to-date
await go('mod', 'tidy');
await go.mod();
} catch (err) {
console.log('failed to `go mod tidy`');
throw err;
@@ -140,11 +209,24 @@ async function build({ files, entrypoint }) {
const destPath = join(outDir, 'handler');
try {
const src = [join(entrypointDirname, mainModGoFileName)];
await go.build({ src, dest: destPath });
await go.build(src, destPath, config.ldsflags);
} catch (err) {
console.log('failed to `go build`');
throw err;
}
if (meta.isDev) {
// caching for `now dev`
await move(
join(entrypointDirname, 'go.mod'),
join(entrypointDirname, 'go.mod.bk'),
{ overwrite: true }
);
await move(
join(entrypointDirname, 'go.sum'),
join(entrypointDirname, 'go.sum.bk'),
{ overwrite: true }
);
}
} else {
const go = await createGo(
goPath,
@@ -153,15 +235,15 @@ async function build({ files, entrypoint }) {
{
cwd: entrypointDirname,
},
false,
false
);
const origianlMainGoContents = await readFile(
join(__dirname, 'main.go'),
'utf8',
'utf8'
);
const mainGoContents = origianlMainGoContents.replace(
'__NOW_HANDLER_FUNC_NAME',
handlerFunctionName,
handlerFunctionName
);
// in order to allow the user to have `main.go`,
@@ -174,6 +256,7 @@ async function build({ files, entrypoint }) {
// `go get` will look at `*.go` (note we set `cwd`), parse the `import`s
// and download any packages that aren't part of the stdlib
console.log('Running `go get`...');
try {
await go.get();
} catch (err) {
@@ -188,7 +271,7 @@ async function build({ files, entrypoint }) {
join(entrypointDirname, mainGoFileName),
downloadedFiles[entrypoint].fsPath,
];
await go.build({ src, dest: destPath });
await go.build(src, destPath);
} catch (err) {
console.log('failed to `go build`');
throw err;
@@ -196,15 +279,27 @@ async function build({ files, entrypoint }) {
}
const lambda = await createLambda({
files: await glob('**', outDir),
files: { ...(await glob('**', outDir)), ...includedFiles },
handler: 'handler',
runtime: 'go1.x',
environment: {},
});
const output = {
[entrypoint]: lambda,
};
let watch = parsedAnalyzed.watch;
// if `entrypoint` located in subdirectory
// we will need to concat it with return watch array
if (entrypointArr.length > 1) {
entrypointArr.pop();
watch = parsedAnalyzed.watch.map(file => join(...entrypointArr, file));
}
return {
[entrypoint]: lambda,
output,
watch,
};
}
module.exports = { config, build };
export { shouldServe };

View File

@@ -1,6 +1,6 @@
{
"name": "@now/go",
"version": "0.4.0",
"version": "0.4.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,9 @@
"directory": "packages/now-go"
},
"scripts": {
"postinstall": "node ./util/install"
"build": "tsc",
"test": "tsc && jest",
"prepublish": "tsc"
},
"files": [
"*.js",
@@ -20,8 +22,15 @@
"debug": "^4.1.1",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"mkdirp-promise": "5.0.1",
"node-fetch": "^2.2.1",
"tar": "4.4.6"
},
"devDependencies": {
"@types/debug": "^4.1.3",
"@types/execa": "^0.9.0",
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"typescript": "^3.4.2"
}
}

View File

@@ -0,0 +1 @@
module build-env

View File

@@ -0,0 +1,17 @@
package buildenv
import (
"fmt"
"net/http"
"os"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_BUILD_ENV")
if rdm == "" {
fmt.Println("No build env received")
}
fmt.Fprintf(w, rdm+":build-env")
}

View File

@@ -0,0 +1 @@
module env

View File

@@ -0,0 +1,17 @@
package env
import (
"fmt"
"net/http"
"os"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_ENV")
if rdm == "" {
fmt.Println("No env received")
}
fmt.Fprintf(w, rdm)
}

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"builds": [
{
"src": "env/index.go",
"use": "@now/go"
}
],
"env": {
"RANDOMNESS_ENV": "RANDOMNESS_PLACEHOLDER"
},
"probes": [
{
"path": "/env",
"mustContain": "RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -0,0 +1,11 @@
package function
import (
"fmt"
"net/http"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,6 @@
{
"version": 2,
"builds": [
{ "src": "index.go", "use": "@now/go" }
]
}

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{ "src": "*.go", "use": "@now/go" }
],
"env": {
"RANDOMNESS_ENV_VAR": "RANDOMNESS_PLACEHOLDER"
}
}

View File

@@ -0,0 +1,16 @@
package function
import (
"net/http"
"os"
"strconv"
)
// HandlerTest1 function
func HandlerTest1(w http.ResponseWriter, r *http.Request) {
rdm := os.Getenv("RANDOMNESS_ENV_VAR")
w.WriteHeader(401)
w.Header().Set("content-length", strconv.Itoa(len(rdm+":content-length")))
w.Write([]byte(rdm + ":content-length"))
}

View File

@@ -0,0 +1,12 @@
package function
import (
"net/http"
)
// HandlerTest2 function
func HandlerTest2(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "2")
w.WriteHeader(401)
w.Write([]byte(""))
}

View File

@@ -0,0 +1,13 @@
package function
import (
"net/http"
"os"
)
// HandlerTest3 function
func HandlerTest3(w http.ResponseWriter, r *http.Request) {
rev := os.Getenv("RANDOMNESS_ENV_VAR")
w.WriteHeader(401)
w.Write([]byte(rev + ":content-length"))
}

View File

@@ -0,0 +1,16 @@
package cowsay
import (
"fmt"
"io/ioutil"
"net/http"
)
// Handler function
func Handler(w http.ResponseWriter, r *http.Request) {
bts, err := ioutil.ReadFile("templates/foo.txt")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, string(bts))
}

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [
{
"src": "index.go",
"use": "@now/go",
"config": {
"includeFiles": [
"templates/**"
]
}
}
],
"probes": [
{
"path": "/",
"mustContain": "foobar from file"
}
]
}

View File

@@ -0,0 +1 @@
foobar from file

View File

@@ -0,0 +1,22 @@
package function
import (
"fmt"
"net/http"
)
// Person struct
type Person struct {
name string
age int
}
// NewPerson struct method
func NewPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
// H func
func H(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{ "src": "index.go", "use": "@now/go" }
],
"probes": [
{ "path": "/", "mustContain": "RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"declaration": false,
"esModuleInterop": true,
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitThis": false,
"types": ["node"],
"strict": true,
"target": "esnext"
}
}

View File

@@ -0,0 +1,169 @@
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
type analyze struct {
PackageName string `json:"packageName"`
FuncName string `json:"functionName"`
Watch []string `json:"watch"`
}
// parse go file
func parse(fileName string) *ast.File {
fset := token.NewFileSet()
parsed, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
if err != nil {
log.Fatalf("Could not parse Go file \"%s\"\n", fileName)
os.Exit(1)
}
return parsed
}
// ensure we only working with interest go file(s)
func visit(files *[]string) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
itf, err := filepath.Match("*test.go", path)
if err != nil {
log.Fatal(err)
}
// we don't need Dirs, or test files
// we only want `.go` files
if info.IsDir() || itf || filepath.Ext(path) != ".go" {
return nil
}
*files = append(*files, path)
return nil
}
}
// return unique file
func unique(files []string) []string {
encountered := map[string]bool{}
for v := range files {
encountered[files[v]] = true
}
result := []string{}
for key := range encountered {
result = append(result, key)
}
return result
}
func main() {
if len(os.Args) != 2 {
// Args should have the program name on `0`
// and the file name on `1`
fmt.Println("Wrong number of args; Usage is:\n ./go-analyze file_name.go")
os.Exit(1)
}
fileName := os.Args[1]
rf, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatal(err)
}
se := string(rf)
var files []string
var relatedFiles []string
// Add entrypoint to watchlist
relFileName, err := filepath.Rel(filepath.Dir(fileName), fileName)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, relFileName)
// looking for all go files that have export func
// using in entrypoint
err = filepath.Walk(filepath.Dir(fileName), visit(&files))
if err != nil {
log.Fatal(err)
}
for _, file := range files {
absFileName, _ := filepath.Abs(fileName)
absFile, _ := filepath.Abs(file)
// if it isn't entrypoint
if absFileName != absFile {
// find all export structs and functions
pf := parse(file)
var exportedDecl []string
ast.Inspect(pf, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.IsExported() {
exportedDecl = append(exportedDecl, t.Name.Name)
}
// find variable declarations
case *ast.TypeSpec:
// which are public
if t.Name.IsExported() {
switch t.Type.(type) {
// and are interfaces
case *ast.StructType:
exportedDecl = append(exportedDecl, t.Name.Name)
}
}
}
return true
})
for _, ed := range exportedDecl {
if strings.Contains(se, ed) {
// find relative path of related file
rel, err := filepath.Rel(filepath.Dir(fileName), file)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, rel)
}
}
}
}
parsed := parse(fileName)
offset := parsed.Pos()
reqRep := "*http.Request http.ResponseWriter"
for _, decl := range parsed.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok {
// this declaration is not a function
// so we're not interested
continue
}
if fn.Name.IsExported() == true {
// find a valid `net/http` handler function
for _, param := range fn.Type.Params.List {
if strings.Contains(reqRep, string(rf[param.Type.Pos()-offset:param.Type.End()-offset])) {
// we found the first exported function with `net/http`
// we're done!
analyzed := analyze{
PackageName: parsed.Name.Name,
FuncName: fn.Name.Name,
Watch: unique(relatedFiles),
}
analyzedJSON, _ := json.Marshal(analyzed)
fmt.Print(string(analyzedJSON))
os.Exit(0)
}
}
}
}
}

View File

@@ -1,41 +0,0 @@
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
)
func main() {
if len(os.Args) != 2 {
// Args should have the program name on `0`
// and the file name on `1`
fmt.Println("Wrong number of args; Usage is:\n ./get-exported-function-name file_name.go")
os.Exit(1)
}
fileName := os.Args[1]
fset := token.NewFileSet()
parsed, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
if err != nil {
fmt.Printf("Could not parse Go file \"%s\"\n", fileName)
os.Exit(1)
}
for _, decl := range parsed.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok {
// this declaraction is not a function
// so we're not interested
continue
}
if fn.Name.IsExported() == true {
// we found the first exported function
// we're done!
fmt.Print(fn.Name.Name, ",", parsed.Name.Name)
os.Exit(0)
}
}
}

View File

@@ -1,18 +0,0 @@
const { join } = require('path');
const { downloadGo } = require('../go-helpers');
async function main() {
// First download the `go` binary for this platform/arch.
const go = await downloadGo();
// Build the `get-exported-function-name` helper program.
// `go get` is not necessary because the program has no external deps.
const src = join(__dirname, 'get-exported-function-name.go');
const dest = join(__dirname, '../get-exported-function-name');
await go.build({ src, dest });
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -1,4 +1,4 @@
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
const { FileBlob, shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const { minify } = require('html-minifier');
const defaultOptions = {
@@ -28,3 +28,5 @@ exports.build = async ({ files, entrypoint, config }) => {
return { [entrypoint]: result };
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/html-minifier",
"version": "1.1.0",
"version": "1.1.3",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,5 +1,6 @@
const { Lambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
const streamToBuffer = require('@now/build-utils/fs/stream-to-buffer.js'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
exports.build = async ({ files, entrypoint, config }) => {
if (!files[entrypoint]) throw new Error('Entrypoint not found in files');
@@ -10,3 +11,5 @@ exports.build = async ({ files, entrypoint, config }) => {
const lambda = new Lambda({ zipBuffer, handler, runtime });
return { [entrypoint]: lambda };
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/lambda",
"version": "0.5.0",
"version": "0.5.3",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,4 +1,4 @@
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
const { FileBlob, shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const unified = require('unified');
const unifiedStream = require('unified-stream');
const markdown = require('remark-parse');
@@ -38,3 +38,5 @@ exports.build = async ({ files, entrypoint, config }) => {
return { [replacedEntrypoint]: result };
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/md",
"version": "0.5.0",
"version": "0.5.3",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -8,9 +8,11 @@ const { runNpmInstall } = require('@now/build-utils/fs/run-user-scripts.js'); //
const writeFile = promisify(fs.writeFile);
exports.build = async ({ files, entrypoint, workPath }) => {
exports.build = async ({
files, entrypoint, workPath, meta,
}) => {
console.log('downloading user files...');
const downloadedFiles = await download(files, workPath);
const downloadedFiles = await download(files, workPath, meta);
console.log('writing package.json...');
const packageJson = { dependencies: { 'mdx-deck': '1.7.15' } };
const packageJsonPath = path.join(workPath, 'package.json');

View File

@@ -1,6 +1,6 @@
{
"name": "@now/mdx-deck",
"version": "0.5.0",
"version": "0.5.4",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "0.2.0",
"version": "0.3.2",
"license": "MIT",
"main": "./dist/index",
"scripts": {
@@ -14,7 +14,7 @@
"directory": "packages/now-next"
},
"dependencies": {
"@now/node-bridge": "^1.1.0",
"@now/node-bridge": "^1.1.2",
"fs-extra": "^7.0.0",
"get-port": "^5.0.0",
"resolve-from": "^5.0.0",

View File

@@ -2,18 +2,21 @@ import resolveFrom from 'resolve-from';
import { parse } from 'url';
import getPort from 'get-port';
import { createServer } from 'http';
import { syncEnvVars } from './utils';
export interface ProcessEnv {
[key: string]: string;
}
process.on('unhandledRejection', err => {
console.error('Exiting builder due to build error:');
console.error(err);
process.exit(1);
});
async function main(env: ProcessEnv, cwd: string) {
async function main(cwd: string) {
const next = require(resolveFrom(cwd, 'next'));
const app = next({ dev: true, dir: cwd });
const handler = app.getRequestHandler();
const openPort = await getPort({
port: [ 5000, 4000 ]
port: [5000, 4000],
});
const url = `http://localhost:${openPort}`;
@@ -21,21 +24,21 @@ async function main(env: ProcessEnv, cwd: string) {
// Prepare for incoming requests
await app.prepare();
// The runtime env vars are passed in to `argv[2]`
// as a base64-encoded JSON string
const runtimeEnv = JSON.parse(
Buffer.from(process.argv[2], 'base64').toString()
);
syncEnvVars(process.env, process.env, runtimeEnv);
createServer((req, res) => {
const parsedUrl = parse(req.url || '', true);
handler(req, res, parsedUrl);
}).listen(openPort, (error: NodeJS.ErrnoException) => {
if (error) {
console.error(error);
process.exit(1);
return;
}
}).listen(openPort, () => {
if (process.send) {
process.send(url);
}
});
}
main(process.env as ProcessEnv, process.cwd());
main(process.cwd());

View File

@@ -25,19 +25,23 @@ import {
import nextLegacyVersions from './legacy-versions';
import {
EnvConfig,
excludeFiles,
getNextConfig,
getPathsInside,
getRoutes,
includeOnlyEntryDirectory,
normalizePackageJson,
onlyStaticDirectory,
filesFromDirectory,
stringMap,
syncEnvVars,
validateEntrypoint,
} from './utils';
interface BuildParamsMeta {
isDev: boolean | undefined;
env?: EnvConfig;
buildEnv?: EnvConfig;
}
interface BuildParamsType extends BuildOptions {
@@ -119,13 +123,17 @@ function isLegacyNext(nextVersion: string) {
const name = '[@now/next]';
const urls: stringMap = {};
function startDevServer(entryPath: string) {
const forked = fork(path.join(__dirname, 'dev-server.js'), [], {
function startDevServer(entryPath: string, runtimeEnv: EnvConfig) {
// The runtime env vars are encoded and passed in as `argv[2]`, so that the
// dev-server process can replace them onto `process.env` after the Next.js
// "prepare" step
const encodedEnv = Buffer.from(JSON.stringify(runtimeEnv)).toString('base64');
// `env` is omitted since that
// makes it default to `process.env`
const forked = fork(path.join(__dirname, 'dev-server.js'), [encodedEnv], {
cwd: entryPath,
execArgv: [],
env: {
NOW_REGION: 'dev1',
},
});
const getUrl = () =>
@@ -182,7 +190,13 @@ export const build = async ({
if (!urls[entrypoint]) {
console.log(`${name} Installing dependencies...`);
await runNpmInstall(entryPath, ['--prefer-offline']);
const { forked, getUrl } = startDevServer(entryPath);
// The runtime env vars consist of the base `process.env` vars, but with the
// build env vars removed, and the runtime env vars mixed in afterwards
const runtimeEnv: EnvConfig = Object.assign({}, process.env);
syncEnvVars(runtimeEnv, meta.buildEnv || {}, meta.env || {});
const { forked, getUrl } = startDevServer(entryPath, runtimeEnv);
urls[entrypoint] = await getUrl();
childProcess = forked;
console.log(
@@ -434,13 +448,30 @@ export const build = async ({
{}
);
const staticDirectoryFiles = onlyStaticDirectory(
includeOnlyEntryDirectory(files, entryDirectory),
entryDirectory
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(
(mappedFiles, file) => ({
...mappedFiles,
[file.replace(/public[/\\]+/, '')]: publicDirectoryFiles[file],
}),
{}
);
return {
output: { ...lambdas, ...staticFiles, ...staticDirectoryFiles },
output: {
...publicFiles,
...lambdas,
...staticFiles,
...staticDirectoryFiles,
},
routes: [],
watch: [],
childProcesses: [],

View File

@@ -1,12 +1,16 @@
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
process.env.NODE_ENV =
process.env.NOW_REGION === 'dev1' ? 'development' : 'production';
}
const { Server } = require('http');
const { Bridge } = require('./now__bridge');
const page = require('./page');
const server = new Server(page.render);
// page.render is for React rendering
// page.default is for /api rendering
// page is for module.exports in /api
const server = new Server(page.render || page.default || page);
const bridge = new Bridge(server);
bridge.listen();

View File

@@ -2,18 +2,22 @@ import fs from 'fs-extra';
import path from 'path';
import { Files } from '@now/build-utils';
type stringMap = {[key: string]: string};
type stringMap = { [key: string]: string };
export interface EnvConfig {
[name: string]: string | undefined;
}
/**
* Validate if the entrypoint is allowed to be used
*/
function validateEntrypoint(entrypoint: string) {
if (
!/package\.json$/.exec(entrypoint)
&& !/next\.config\.js$/.exec(entrypoint)
!/package\.json$/.exec(entrypoint) &&
!/next\.config\.js$/.exec(entrypoint)
) {
throw new Error(
'Specified "src" for "@now/next" has to be "package.json" or "next.config.js"',
'Specified "src" for "@now/next" has to be "package.json" or "next.config.js"'
);
}
}
@@ -21,7 +25,10 @@ function validateEntrypoint(entrypoint: string) {
/**
* Exclude certain files from the files object
*/
function excludeFiles(files: Files, matcher: (filePath: string) => boolean): Files {
function excludeFiles(
files: Files,
matcher: (filePath: string) => boolean
): Files {
return Object.keys(files).reduce((newFiles, filePath) => {
if (matcher(filePath)) {
return newFiles;
@@ -36,7 +43,10 @@ function excludeFiles(files: Files, matcher: (filePath: string) => boolean): Fil
/**
* Creates a new Files object holding only the entrypoint files
*/
function includeOnlyEntryDirectory(files: Files, entryDirectory: string): Files {
function includeOnlyEntryDirectory(
files: Files,
entryDirectory: string
): Files {
if (entryDirectory === '.') {
return files;
}
@@ -63,11 +73,11 @@ function excludeLockFiles(files: Files): Files {
}
/**
* Include the static directory from files
* Include only the files from a selected directory
*/
function onlyStaticDirectory(files: Files, entryDir: string): Files {
function filesFromDirectory(files: Files, dir: string): Files {
function matcher(filePath: string) {
return !filePath.startsWith(path.join(entryDir, 'static'));
return !filePath.startsWith(dir.replace(/\\/g, '/'));
}
return excludeFiles(files, matcher);
@@ -76,7 +86,13 @@ function onlyStaticDirectory(files: Files, entryDir: string): Files {
/**
* Enforce specific package.json configuration for smallest possible lambda
*/
function normalizePackageJson(defaultPackageJson: {dependencies?: stringMap, devDependencies?: stringMap, scripts?: stringMap} = {}) {
function normalizePackageJson(
defaultPackageJson: {
dependencies?: stringMap;
devDependencies?: stringMap;
scripts?: stringMap;
} = {}
) {
const dependencies: stringMap = {};
const devDependencies: stringMap = {
...defaultPackageJson.dependencies,
@@ -112,7 +128,8 @@ function normalizePackageJson(defaultPackageJson: {dependencies?: stringMap, dev
},
scripts: {
...defaultPackageJson.scripts,
'now-build': 'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
'now-build':
'NODE_OPTIONS=--max_old_space_size=3000 next build --lambdas',
},
};
}
@@ -151,7 +168,12 @@ function getPathsInside(entryDirectory: string, files: Files) {
return watch;
}
function getRoutes(entryDirectory: string, pathsInside: string[], files: Files, url: string): any[] {
function getRoutes(
entryDirectory: string,
pathsInside: string[],
files: Files,
url: string
): any[] {
const filesInside: Files = {};
const prefix = entryDirectory === `.` ? `/` : `/${entryDirectory}/`;
@@ -166,15 +188,16 @@ function getRoutes(entryDirectory: string, pathsInside: string[], files: Files,
const routes: any[] = [
{
src: `${prefix}_next/(.*)`,
dest: `${url}/_next/$1`
dest: `${url}/_next/$1`,
},
{
src: `${prefix}static/(.*)`,
dest: `${url}/static/$1`
}
dest: `${url}/static/$1`,
},
];
const filePaths = Object.keys(filesInside);
for (const file of Object.keys(filesInside)) {
for (const file of filePaths) {
const relativePath = path.relative(entryDirectory, file);
const isPage = pathIsInside('pages', relativePath);
@@ -192,7 +215,7 @@ function getRoutes(entryDirectory: string, pathsInside: string[], files: Files,
routes.push({
src: `${prefix}${pageName}`,
dest: `${url}/${pageName}`
dest: `${url}/${pageName}`,
});
if (pageName.endsWith('index')) {
@@ -200,23 +223,57 @@ function getRoutes(entryDirectory: string, pathsInside: string[], files: Files,
routes.push({
src: `${prefix}${resolvedIndex}`,
dest: `${url}/${resolvedIndex}`
dest: `${url}/${resolvedIndex}`,
});
}
}
// Add public folder routes
for (const file of filePaths) {
const relativePath = path.relative(entryDirectory, file);
const isPublic = pathIsInside('public', relativePath);
if (!isPublic) continue;
const fileName = path.relative('public', relativePath);
const route = {
src: `${prefix}${fileName}`,
dest: `${url}/${fileName}`,
};
// Only add the route if a page is not already using it
if (!routes.some(r => r.src === route.src)) {
routes.push(route);
}
}
return routes;
}
function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
// Remove any env vars from `removeEnv`
// that are not present in the `addEnv`
const addKeys = new Set(Object.keys(addEnv));
for (const name of Object.keys(removeEnv)) {
if (!addKeys.has(name)) {
delete base[name];
}
}
// Add in the keys from `addEnv`
Object.assign(base, addEnv);
}
export {
excludeFiles,
validateEntrypoint,
includeOnlyEntryDirectory,
excludeLockFiles,
normalizePackageJson,
onlyStaticDirectory,
filesFromDirectory,
getNextConfig,
getPathsInside,
getRoutes,
stringMap,
syncEnvVars,
};

View File

@@ -28,6 +28,7 @@ it(
} = await runBuildLambda(path.join(__dirname, 'monorepo'));
expect(output['www/index']).toBeDefined();
expect(output['www/static/test.txt']).toBeDefined();
expect(output['www/data.txt']).toBeDefined();
const filePaths = Object.keys(output);
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
const hasUnderScoreErrorStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_error\.js$/));
@@ -96,3 +97,14 @@ it(
},
FOUR_MINUTES,
);
it(
'Should build the public-files test',
async () => {
const {
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'public-files'));
expect(output['robots.txt']).toBeDefined();
},
FOUR_MINUTES,
);

View File

@@ -0,0 +1 @@
data

View File

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

View File

@@ -0,0 +1,6 @@
{
"version": 2,
"builds": [
{"src": "next.config.js", "use": "@now/next"}
]
}

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"now-build": "next build"
},
"dependencies": {
"next": "8",
"react": "16",
"react-dom": "16"
}
}

View File

@@ -0,0 +1 @@
export default () => 'Index page';

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@@ -2,7 +2,7 @@
const path = require('path');
const os = require('os');
const { build } = require('@now/next');
const { FileBlob } = require('@now/build-utils');
const { download, FileBlob } = require('@now/build-utils');
jest.setTimeout(45000);
@@ -22,6 +22,15 @@ describe('build meta dev', () => {
export default () => 'Index page'
`,
}),
// This file should be omitted because `pages/index.js` will use the same route
'public/index': new FileBlob({
mode: 0o777,
data: 'text',
}),
'public/data.txt': new FileBlob({
mode: 0o777,
data: 'data',
}),
'package.json': new FileBlob({
mode: 0o777,
data: `
@@ -46,24 +55,12 @@ describe('build meta dev', () => {
.slice(3),
);
console.log('workPath directory: ', workPath);
/*
it('should have builder v2 response isDev=false', async () => {
const meta = { isDev: false, requestPath: null };
const { output, routes, watch } = await build({
files,
workPath,
entrypoint,
meta,
});
//console.log('output: ', Object.keys(output));
expect(Object.keys(output).length).toBe(7);
expect(output.index.type).toBe('Lambda');
expect(routes.length).toBe(0);
expect(watch.length).toBe(0);
});
*/
it('should have builder v2 response isDev=true', async () => {
// Since `download()` is a no-op when `isDev=true`, the assumption is that the
// source files are already present, so manually download them here first.
await download(files, workPath);
const meta = { isDev: true, requestPath: null };
const {
output, routes, watch, childProcesses,
@@ -83,8 +80,15 @@ describe('build meta dev', () => {
{ src: '/static/(.*)', dest: 'http://localhost:5000/static/$1' },
{ src: '/index', dest: 'http://localhost:5000/index' },
{ src: '/', dest: 'http://localhost:5000/' },
{ src: '/data.txt', dest: 'http://localhost:5000/data.txt' },
]);
expect(watch).toEqual([
'next.config.js',
'pages/index.js',
'public/index',
'public/data.txt',
'package.json',
]);
expect(watch).toEqual(['next.config.js', 'pages/index.js', 'package.json']);
childProcesses.forEach(cp => cp.kill());
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node-bridge",
"version": "1.1.0",
"version": "1.1.2",
"license": "MIT",
"main": "./index.js",
"repository": {

View File

@@ -9,6 +9,7 @@ const {
runNpmInstall,
runPackageJsonScript,
} = require('@now/build-utils/fs/run-user-scripts.js'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
/** @typedef {{[filePath: string]: FileRef}} Files */
@@ -27,11 +28,13 @@ const {
* @param {string[]} [options.npmArguments]
*/
async function downloadInstallAndBundle(
{ files, entrypoint, workPath },
{
files, entrypoint, workPath, meta,
},
{ npmArguments = [] } = {},
) {
console.log('downloading user files...');
const downloadedFiles = await download(files, workPath);
const downloadedFiles = await download(files, workPath, meta);
console.log("installing dependencies for user's code...");
const entrypointFsDirname = path.join(workPath, path.dirname(entrypoint));
@@ -49,8 +52,12 @@ async function compile(workPath, downloadedFiles, entrypoint, config) {
});
if (config && config.includeFiles) {
const includeFiles = typeof config.includeFiles === 'string'
? [config.includeFiles]
: config.includeFiles;
// eslint-disable-next-line no-restricted-syntax
for (const pattern of config.includeFiles) {
for (const pattern of includeFiles) {
// eslint-disable-next-line no-await-in-loop
const files = await glob(pattern, inputDir);
@@ -94,10 +101,12 @@ exports.config = {
* @returns {Promise<Files>}
*/
exports.build = async ({
files, entrypoint, config, workPath,
files, entrypoint, config, workPath, meta,
}) => {
const [downloadedFiles, entrypointFsDirname] = await downloadInstallAndBundle(
{ files, entrypoint, workPath },
{
files, entrypoint, workPath, meta,
},
{ npmArguments: ['--prefer-offline'] },
);
@@ -145,3 +154,5 @@ exports.prepareCache = async ({ workPath }) => ({
...(await glob('package-lock.json', workPath)),
...(await glob('yarn.lock', workPath)),
});
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node-server",
"version": "0.6.0",
"version": "0.7.2",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,8 +8,8 @@
"directory": "packages/now-node-server"
},
"dependencies": {
"@now/node-bridge": "^1.1.0",
"@zeit/ncc": "0.18.1",
"@now/node-bridge": "^1.1.2",
"@zeit/ncc": "0.18.5",
"fs-extra": "7.0.1"
},
"scripts": {

View File

@@ -0,0 +1,5 @@
const express = require('express');
const app = express();
app.listen();

View File

@@ -9,6 +9,13 @@
"templates/**"
]
}
},
{
"src": "accepts-string/index.js",
"use": "@now/node-server",
"config": {
"includeFiles": "templates/**"
}
}
],
"probes": [

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "0.6.0",
"version": "0.7.2",
"license": "MIT",
"main": "./dist/index",
"repository": {
@@ -9,8 +9,8 @@
"directory": "packages/now-node"
},
"dependencies": {
"@now/node-bridge": "^1.1.0",
"@zeit/ncc": "0.18.1",
"@now/node-bridge": "^1.1.2",
"@zeit/ncc": "0.18.5",
"fs-extra": "7.0.1"
},
"scripts": {

View File

@@ -6,21 +6,24 @@ import {
FileBlob,
FileFsRef,
Files,
Meta,
createLambda,
runNpmInstall,
runPackageJsonScript,
PrepareCacheOptions,
BuildOptions,
shouldServe,
} from '@now/build-utils';
interface CompilerConfig {
includeFiles?: string[]
includeFiles?: string | string[];
}
interface DownloadOptions {
files: Files,
files: Files;
entrypoint: string;
workPath: string;
meta?: Meta;
npmArguments?: string[];
}
@@ -28,10 +31,11 @@ async function downloadInstallAndBundle({
files,
entrypoint,
workPath,
npmArguments = []
meta,
npmArguments = [],
}: DownloadOptions) {
console.log('downloading user files...');
const downloadedFiles = await download(files, workPath);
const downloadedFiles = await download(files, workPath, meta);
console.log("installing dependencies for user's code...");
const entrypointFsDirname = join(workPath, dirname(entrypoint));
@@ -41,15 +45,27 @@ async function downloadInstallAndBundle({
return { entrypointPath, entrypointFsDirname };
}
async function compile(entrypointPath: string, entrypoint: string, config: CompilerConfig): Promise<Files> {
async function compile(
entrypointPath: string,
entrypoint: string,
config: CompilerConfig
): Promise<Files> {
const input = entrypointPath;
const inputDir = dirname(input);
const rootIncludeFiles = inputDir.split(sep).pop() || '';
const ncc = require('@zeit/ncc');
const { code, map, assets } = await ncc(input, { sourceMap: true, sourceMapRegister: true });
const { code, map, assets } = await ncc(input, {
sourceMap: true,
sourceMapRegister: true,
});
if (config && config.includeFiles) {
for (const pattern of config.includeFiles) {
const includeFiles =
typeof config.includeFiles === 'string'
? [config.includeFiles]
: config.includeFiles;
for (const pattern of includeFiles) {
const files = await glob(pattern, inputDir);
for (const assetName of Object.keys(files)) {
@@ -61,12 +77,12 @@ async function compile(entrypointPath: string, entrypoint: string, config: Compi
// if asset contain directory
// no need to use `rootIncludeFiles`
if (assetName.includes(sep)) {
fullPath = assetName
fullPath = assetName;
}
assets[fullPath] = {
'source': data,
'permissions': mode
source: data,
permissions: mode,
};
}
}
@@ -75,7 +91,9 @@ async function compile(entrypointPath: string, entrypoint: string, config: Compi
const preparedFiles: Files = {};
// move all user code to 'user' subdirectory
preparedFiles[entrypoint] = new FileBlob({ data: code });
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({ data: map });
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({
data: map,
});
// eslint-disable-next-line no-restricted-syntax
for (const assetName of Object.keys(assets)) {
const { source: data, permissions: mode } = assets[assetName];
@@ -87,16 +105,26 @@ async function compile(entrypointPath: string, entrypoint: string, config: Compi
}
export const config = {
maxLambdaSize: '5mb'
maxLambdaSize: '5mb',
};
export async function build({ files, entrypoint, workPath, config }: BuildOptions) {
export async function build({
files,
entrypoint,
workPath,
config,
meta,
}: BuildOptions) {
const {
entrypointPath,
entrypointFsDirname
} = await downloadInstallAndBundle(
{ files, entrypoint, workPath, npmArguments: ['--prefer-offline'] }
);
entrypointFsDirname,
} = await downloadInstallAndBundle({
files,
entrypoint,
workPath,
meta,
npmArguments: ['--prefer-offline'],
});
console.log('running user script...');
await runPackageJsonScript(entrypointFsDirname, 'now-build');
@@ -110,19 +138,19 @@ export async function build({ files, entrypoint, workPath, config }: BuildOption
'// PLACEHOLDER',
[
`listener = require("./${entrypoint}");`,
'if (listener.default) listener = listener.default;'
'if (listener.default) listener = listener.default;',
].join(' ')
);
const launcherFiles = {
'launcher.js': new FileBlob({ data: launcherData }),
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') })
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
};
const lambda = await createLambda({
files: { ...preparedFiles, ...launcherFiles },
handler: 'launcher.launcher',
runtime: 'nodejs8.10'
runtime: 'nodejs8.10',
});
return { [entrypoint]: lambda };
@@ -132,6 +160,8 @@ export async function prepareCache({ workPath }: PrepareCacheOptions) {
return {
...(await glob('node_modules/**', workPath)),
...(await glob('package-lock.json', workPath)),
...(await glob('yarn.lock', workPath))
...(await glob('yarn.lock', workPath)),
};
}
export { shouldServe };

View File

@@ -0,0 +1,7 @@
const edge = require('edge.js');
module.exports = (req, resp) => {
edge.registerViews('templates');
resp.end(edge.render('accepts-string', { name: 'String!' }));
};

View File

@@ -18,6 +18,13 @@
"root.edge"
]
}
},
{
"src": "accepts-string.js",
"use": "@now/node",
"config": {
"includeFiles": "templates/accepts-string.edge"
}
}
],
"probes": [
@@ -25,6 +32,10 @@
"path": "/",
"mustContain": "hello Now!"
},
{
"path": "/accepts-string.js",
"mustContain": "hello String!"
},
{
"path": "/root.js",
"mustContain": "hello Root!"

View File

@@ -0,0 +1 @@
hello {{ name }}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/optipng",
"version": "0.5.0",
"version": "0.6.1",
"license": "MIT",
"main": "./dist/index",
"files": [

View File

@@ -1,13 +1,14 @@
// eslint-disable-line import/no-extraneous-dependencies
import {
import {
FileBlob,
BuildOptions,
AnalyzeOptions
} from '@now/build-utils'
import OptiPng from 'optipng'
import pipe from 'multipipe'
AnalyzeOptions,
shouldServe,
} from '@now/build-utils';
import OptiPng from 'optipng';
import pipe from 'multipipe';
export function analyze({ files, entrypoint }: AnalyzeOptions) {
export function analyze({ files, entrypoint }: AnalyzeOptions) {
return files[entrypoint].digest;
}
@@ -20,3 +21,5 @@ export async function build({ files, entrypoint }: BuildOptions) {
const result = await FileBlob.fromStream({ stream });
return { [entrypoint]: result };
}
export { shouldServe };

View File

@@ -1,6 +1,6 @@
{
"name": "@now/php-bridge",
"version": "0.5.0",
"version": "0.5.2",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,5 +1,9 @@
const {
createLambda, rename, glob, download,
createLambda,
rename,
glob,
download,
shouldServe,
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const path = require('path');
const { getFiles } = require('@now/php-bridge');
@@ -9,11 +13,10 @@ exports.config = {
};
exports.build = async ({
files, entrypoint, workPath, config,
files, entrypoint, workPath, config, meta,
}) => {
// Download all files to workPath
const fileDir = path.join(workPath, 'userfiles');
const downloadedFiles = await download(files, fileDir);
const downloadedFiles = await download(files, workPath, meta);
let includedFiles = {};
if (config && config.includeFiles) {
@@ -21,7 +24,7 @@ exports.build = async ({
// eslint-disable-next-line no-restricted-syntax
for (const pattern of config.includeFiles) {
// eslint-disable-next-line no-await-in-loop
const matchedFiles = await glob(pattern, fileDir);
const matchedFiles = await glob(pattern, workPath);
Object.assign(includedFiles, matchedFiles);
}
// explicit and always include the entrypoint
@@ -52,3 +55,5 @@ exports.build = async ({
return { [entrypoint]: lambda };
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/php",
"version": "0.5.0",
"version": "0.5.4",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,7 @@
"directory": "packages/now-php"
},
"dependencies": {
"@now/php-bridge": "^0.5.0"
"@now/php-bridge": "^0.5.2"
},
"scripts": {
"test": "jest"

View File

@@ -1,56 +0,0 @@
import { join } from 'path';
import fetch from 'node-fetch';
import execa from 'execa';
import { createWriteStream } from 'fs';
import { getWriteableDirectory } from '@now/build-utils';
const url = 'https://bootstrap.pypa.io/get-pip.py';
// downloads `get-pip.py` and returns its absolute path
async function downloadGetPipScript() {
console.log('downloading "get-pip.py"...');
const res = await fetch(url);
if (!res.ok || res.status !== 200) {
throw new Error(`Could not download "get-pip.py" from "${url}"`);
}
const dir = await getWriteableDirectory();
const filePath = join(dir, 'get-pip.py');
const writeStream = createWriteStream(filePath);
return new Promise<string>((resolve, reject) => {
res.body
.on('error', reject)
.pipe(writeStream)
.on('finish', () => resolve(filePath));
});
}
// downloads and installs `pip` (respecting
// process.env.PYTHONUSERBASE), and returns
// the absolute path to it
export async function downloadAndInstallPip() {
const { PYTHONUSERBASE } = process.env;
if (!PYTHONUSERBASE) {
// this is the directory in which `pip` will be
// installed to. `--user` will assume `~` if this
// is not set, and `~` is not writeable on AWS Lambda.
// let's refuse to proceed
throw new Error(
'Could not install "pip": "PYTHONUSERBASE" env var is not set',
);
}
const getPipFilePath = await downloadGetPipScript();
console.log('running "python get-pip.py"...');
try {
await execa('python3', [getPipFilePath, '--user'], { stdio: 'inherit' });
} catch (err) {
console.log('could not install pip');
throw err;
}
return join(PYTHONUSERBASE, 'bin', 'pip');
}

View File

@@ -9,20 +9,30 @@ import {
download,
glob,
createLambda,
shouldServe,
BuildOptions,
} from '@now/build-utils';
import { downloadAndInstallPip } from './download-and-install-pip';
async function pipInstall(pipPath: string, workDir: string, ...args: string[]) {
const target = '.';
console.log(`running "pip install --target ${target} --upgrade ${args.join(' ')}"...`);
console.log(
`running "pip install --target ${target} --upgrade ${args.join(' ')}"...`
);
try {
await execa(pipPath, ['install', '--target', target, '--upgrade', ...args], {
cwd: workDir,
stdio: 'inherit',
});
await execa(
pipPath,
['install', '--target', target, '--upgrade', ...args],
{
cwd: workDir,
stdio: 'inherit',
}
);
} catch (err) {
console.log(`failed to run "pip install --target ${target} --upgrade ${args.join(' ')}"...`);
console.log(
`failed to run "pip install --target ${target} --upgrade ${args.join(
' '
)}"...`
);
throw err;
}
}
@@ -42,11 +52,10 @@ async function pipInstallUser(pipPath: string, ...args: string[]) {
async function pipenvInstall(pyUserBase: string, srcDir: string) {
console.log('running "pipenv_to_requirements -f');
try {
await execa(
join(pyUserBase, 'bin', 'pipenv_to_requirements'),
['-f'],
{ cwd: srcDir, stdio: 'inherit' },
);
await execa(join(pyUserBase, 'bin', 'pipenv_to_requirements'), ['-f'], {
cwd: srcDir,
stdio: 'inherit',
});
} catch (err) {
console.log('failed to run "pipenv_to_requirements -f"');
throw err;
@@ -57,17 +66,18 @@ export const config = {
maxLambdaSize: '5mb',
};
export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
export const build = async ({
workPath,
files: originalFiles,
entrypoint,
meta = {},
}: BuildOptions) => {
console.log('downloading files...');
// eslint-disable-next-line no-param-reassign
files = await download(files, workPath);
// this is where `pip` will be installed to
// we need it to be under `/tmp`
const downloadedFiles = await download(originalFiles, workPath, meta);
const foundLockFile = 'Pipfile.lock' in downloadedFiles;
const pyUserBase = await getWriteableDirectory();
process.env.PYTHONUSERBASE = pyUserBase;
const pipPath = await downloadAndInstallPip();
const pipPath = 'pip3';
try {
// See: https://stackoverflow.com/a/44728772/376773
@@ -77,8 +87,10 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
//
// distutils.errors.DistutilsOptionError: must supply either home
// or prefix/exec-prefix -- not both
const setupCfg = join(workPath, 'setup.cfg');
await writeFile(setupCfg, '[install]\nprefix=\n');
if (meta.isDev) {
const setupCfg = join(workPath, 'setup.cfg');
await writeFile(setupCfg, '[install]\nprefix=\n');
}
} catch (err) {
console.log('failed to create "setup.cfg" file');
throw err;
@@ -87,10 +99,7 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
await pipInstall(pipPath, workPath, 'werkzeug');
await pipInstall(pipPath, workPath, 'requests');
const entryDirectory = dirname(entrypoint);
const requirementsTxt = join(entryDirectory, 'requirements.txt');
if (files['Pipfile.lock']) {
if (foundLockFile) {
console.log('found "Pipfile.lock"');
// Install pipenv.
@@ -100,6 +109,8 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
}
const fsFiles = await glob('**', workPath);
const entryDirectory = dirname(entrypoint);
const requirementsTxt = join(entryDirectory, 'requirements.txt');
if (fsFiles[requirementsTxt]) {
console.log('found local "requirements.txt"');
@@ -122,7 +133,7 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
.replace(/\.py$/, '');
const nowHandlerPyContents = originalNowHandlerPyContents.replace(
/__NOW_HANDLER_FILENAME/g,
userHandlerFilePath,
userHandlerFilePath
);
// in order to allow the user to have `server.py`, we need our `server.py` to be called
@@ -131,7 +142,7 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
await writeFile(
join(workPath, `${nowHandlerPyFilename}.py`),
nowHandlerPyContents,
nowHandlerPyContents
);
const lambda = await createLambda({
@@ -145,3 +156,5 @@ export const build = async ({ workPath, files, entrypoint }: BuildOptions) => {
[entrypoint]: lambda,
};
};
export { shouldServe };

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "0.1.0",
"version": "0.2.2",
"main": "index.js",
"license": "MIT",
"repository": {

View File

@@ -8,6 +8,7 @@ const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line impo
const { runShellScript } = require('@now/build-utils/fs/run-user-scripts.js'); // eslint-disable-line import/no-extraneous-dependencies
const FileFsRef = require('@now/build-utils/file-fs-ref.js'); // eslint-disable-line import/no-extraneous-dependencies
const FileRef = require('@now/build-utils/file-ref.js'); // eslint-disable-line import/no-extraneous-dependencies
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
const installRust = require('./install-rust.js');
exports.config = {
@@ -253,10 +254,10 @@ async function buildSingleFile({
exports.build = async (m) => {
const {
files, entrypoint, workPath, config,
files, entrypoint, workPath, config, meta,
} = m;
console.log('downloading files');
const downloadedFiles = await download(files, workPath);
const downloadedFiles = await download(files, workPath, meta);
const entryPath = downloadedFiles[entrypoint].fsPath;
await installRust();
@@ -367,3 +368,5 @@ exports.getDefaultCache = ({ files, entrypoint }) => {
});
return { [targetFolderDir]: defaultCacheRef };
};
exports.shouldServe = shouldServe;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/rust",
"version": "0.2.0",
"version": "0.2.4",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,9 +1,8 @@
const path = require('path');
const { spawn } = require('child_process');
const getPort = require('get-port');
const { promisify } = require('util');
const { timeout } = require('promise-timeout');
const { existsSync, readFileSync } = require('fs');
const waitForPort = promisify(require('wait-for-port'));
const {
glob,
download,
@@ -16,7 +15,7 @@ function validateDistDir(distDir) {
const distDirName = path.basename(distDir);
if (!existsSync(distDir)) {
const message = `Build was unable to create the distDir: ${distDirName}.`
+ '\nMake sure you mentioned the correct dist directory: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#configuring-the-build-output-directory';
+ '\nMake sure you mentioned the correct dist directory: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development';
throw new Error(message);
}
}
@@ -59,28 +58,34 @@ exports.build = async ({
devPort = await getPort();
nowDevScriptPorts.set(entrypoint, devPort);
const opts = {
cwd: entrypointFsDirname,
env: { ...process.env, PORT: String(devPort) },
};
const promise = runPackageJsonScript(
entrypointFsDirname,
'now-dev',
opts,
);
promise.then(
() => {
nowDevScriptPorts.delete(entrypoint);
},
(err) => {
console.log('`now-dev` script error:', err);
nowDevScriptPorts.delete(entrypoint);
},
);
const child = spawn('yarn', ['run', 'now-dev'], opts);
child.on('exit', () => nowDevScriptPorts.delete(entrypoint));
child.stdout.setEncoding('utf8');
child.stdout.pipe(process.stdout);
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(waitForPort('localhost', devPort), 60 * 1000);
await timeout(
new Promise((resolve) => {
const checkForPort = (data) => {
// Check the logs for the URL being printed with the port number
// (i.e. `http://localhost:47521`).
if (data.indexOf(`:${devPort}`) !== -1) {
resolve();
}
};
child.stdout.on('data', checkForPort);
child.stderr.on('data', checkForPort);
}),
5 * 60 * 1000,
);
} catch (err) {
throw new Error(
`Failed to detect a server running on port ${devPort}`,
@@ -99,6 +104,12 @@ exports.build = async ({
dest: `http://localhost:${devPort}${srcBase}/$1`,
});
} else {
if (meta.isDev) {
console.log('WARN: "now-dev" script is missing from package.json');
console.log(
'See the local development docs: http://zeit.co/docs/v2/deployments/official-builders/static-now-static#local-development',
);
}
// Run the `now-build` script and wait for completion to collect the build
// outputs
console.log('running user "now-build" script from `package.json`...');

View File

@@ -1,6 +1,6 @@
{
"name": "@now/static-build",
"version": "0.5.0",
"version": "0.5.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -12,7 +12,6 @@
},
"dependencies": {
"get-port": "5.0.0",
"promise-timeout": "1.3.0",
"wait-for-port": "0.0.2"
"promise-timeout": "1.3.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/wordpress",
"version": "0.5.0",
"version": "0.5.2",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,7 @@
"directory": "packages/now-wordpress"
},
"dependencies": {
"@now/php-bridge": "^0.5.0",
"@now/php-bridge": "^0.5.2",
"node-fetch": "2.3.0",
"yauzl": "2.10.0"
},

View File

@@ -28,11 +28,27 @@ async function runBuildLambda(inputPath) {
entrypoint,
config: build.config,
});
console.log(analyzeResult);
const workPath = await getWritableDirectory();
const buildResult = await wrapper.build({
files: inputFiles, entrypoint, config: build.config, workPath,
files: inputFiles,
entrypoint,
config: build.config,
workPath,
});
const { output } = buildResult;
// Windows support
if (output) {
buildResult.output = Object.keys(output).reduce(
(result, path) => ({
...result,
[path.replace(/\\/g, '/')]: output[path],
}),
{},
);
}
return {
analyzeResult,
buildResult,

View File

@@ -940,6 +940,18 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/cross-spawn@6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.0.tgz#320aaf1d1a12979f1b84fe7a5590a7e860bf3a80"
integrity sha512-evp2ZGsFw9YKprDbg8ySgC9NA15g3YgiI8ANkGmKKvvi0P2aDGYLPxQIC5qfeKNUOe3TjABVGuah6omPRpIYhg==
dependencies:
"@types/node" "*"
"@types/debug@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.4.tgz#56eec47706f0fd0b7c694eae2f3172e6b0b769da"
integrity sha512-D9MyoQFI7iP5VdpEyPZyjjqIJ8Y8EDNQFIFVLOmeg1rI1xiHOChyUPMPRUVfqFCerxfE+yS3vMyj37F6IdtOoQ==
"@types/end-of-stream@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@types/end-of-stream/-/end-of-stream-1.4.0.tgz#4e73ac87d15b6cc89cdaf2d26a59f617c778cb07"
@@ -1015,6 +1027,13 @@
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.3.0":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.3.2.tgz#e01893b176c6fa1367743726380d65bce5d6576b"
integrity sha512-yW0EOebSsQme9yKu09XbdDfle4/SmWZMK4dfteWcSLCYNQQcF+YOv0kIrvm+9pO11/ghA4E6A+RNQqvYj4Nr3A==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^10.12.8":
version "10.12.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.10.tgz#4fa76e6598b7de3f0cb6ec3abacc4f59e5b3a2ce"
@@ -1068,6 +1087,13 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
"@types/tar@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.0.tgz#e3239d969eeb693a012200613860d0eb871c94f0"
integrity sha512-YybbEHNngcHlIWVCYsoj7Oo1JU9JqONuAlt1LlTH/lmL8BMhbzdFUgReY87a05rY1j8mfK47Del+TCkaLAXwLw==
dependencies:
"@types/node" "*"
"@types/uglify-js@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
@@ -1109,10 +1135,10 @@
globby "8.0.0"
signal-exit "3.0.2"
"@zeit/ncc@0.18.1":
version "0.18.1"
resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.18.1.tgz#1723884210c792ba702ec6dccb390f387f0a3ba6"
integrity sha512-Tq13BzK+hAWBZY+VvncRZzpE5PHGks3kn2XJ+bcWSXgTZb4rTR/CTV1YYXilNNkX//jC1sziuM167FhLzFu2XA==
"@zeit/ncc@0.18.5":
version "0.18.5"
resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.18.5.tgz#5687df6c32f1a2e2486aa110b3454ccebda4fb9c"
integrity sha512-F+SbvEAh8rchiRXqQbmD1UmbePY7dCOKTbvfFtbVbK2xMH/tyri5YKfNxXKK7eL9EWkkbqB3NTVQO6nokApeBA==
JSONStream@^1.0.4, JSONStream@^1.3.4:
version "1.3.5"
@@ -2515,16 +2541,7 @@ create-error-class@^3.0.1:
dependencies:
capture-stack-trace "^1.0.0"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -2535,6 +2552,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
@@ -7003,14 +7029,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp-promise@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
integrity sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=
dependencies:
mkdirp "*"
mkdirp@*, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -9605,6 +9624,11 @@ typescript@3.3.4000:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0"
integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==
typescript@^3.4.2:
version "3.4.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.4.tgz#aac4a08abecab8091a75f10842ffa0631818f785"
integrity sha512-xt5RsIRCEaf6+j9AyOBgvVuAec0i92rgCaS3S+UVf5Z/vF2Hvtsw08wtUTJqp4djwznoAgjSxeCcU4r+CcDBJA==
typescript@^3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
@@ -9949,11 +9973,6 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
wait-for-port@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wait-for-port/-/wait-for-port-0.0.2.tgz#bb5ff253436b9933ab9d65c00d584dc68c4d531a"
integrity sha1-u1/yU0NrmTOrnWXADVhNxoxNUxo=
walker@^1.0.7, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"