Compare commits

...

13 Commits

Author SHA1 Message Date
Nathan Rajlich
0da7197c3e Publish
- @now/build-utils@0.4.37-canary.3
 - @now/go@0.3.1-canary.2
 - @now/html-minifier@1.0.8-canary.1
 - @now/md@0.4.10-canary.2
 - @now/next@0.1.3-canary.2
 - @now/node-bridge@1.0.1-canary.1
 - @now/node-server@0.5.2-canary.3
 - @now/node@0.5.2-canary.5
 - @now/rust@0.1.2-canary.1
2019-03-22 14:44:44 -07:00
Nathan Rajlich
950a4e98e9 [now-node-server] Print a more helpful message when a module is missing (#313)
Similar to #312, but for `@now/node-server`.
2019-03-22 14:32:46 -07:00
Nathan Rajlich
8258ede23f [now-node] Print a more helpful message when a module is missing (#312) 2019-03-22 14:32:17 -07:00
Steven
77f84fe2aa Enhance TS declarations (#311)
This adds a few new exports to `@now/build-utils` with proper documentation 😄 

- AnalyzeOptions
- BuildOptions
- PrepareCacheOptions

I also updated TS is `now-node` and `now-node-bridge`.

### Screenshot

Here's an example of what the docs look like with TS Intellisense 📖 

![image](https://user-images.githubusercontent.com/229881/54851181-80825380-4cbf-11e9-9703-2ca75e8cdf47.png)
2019-03-22 17:31:43 -04:00
Igor Klopov
5c4b946864 Publish
- @now/build-utils@0.4.37-canary.2
2019-03-22 19:41:23 +03:00
Steven
dfc51ad97f Add tracing priority to tests (#310) 2019-03-22 10:09:45 -04:00
Steven
d32afc8332 [now-build-utils] Add typescript (#305)
* Add typescript to build utils

* Move file to file.ts

* Move to src directory

* Cast to readable

* Cast to stats

* Ignore js files

* Remove includes

* Run Linting after Building in CircleCI

* Move now-next tsconfig, add build script

* Fix default exports

* Attemp to fix default exports

* Change api to use index

* Add types to package.json

* Add missing fs

* Add shims

* Add missing end-of-stream dep

* Fix shims

* Ignore TS when linting

* Removed the unused top-level typescript
2019-03-21 18:23:05 -04:00
Sophearak Tha
9d1263ccc2 [now-go] better way to init go.mod (#304) 2019-03-21 09:33:24 +07:00
Matias Larsson
7bf2cfb3dc Remove collapseInlineTagWhitespace option (#281)
`collapseInlineTagWhitespace` removes white space between inline elements so `<p>Text with <a href="https://zeit.co/now">a link</a> is hard to read.</p>` renders as `Text witha linkis hard to read.`.

See example at https://runkit.com/5c8a1c7a0f950f0012252fbb/5c8a1c7a0f950f0012252fbc
2019-03-20 16:06:02 -04:00
Sophearak Tha
9b37460c4f [now-node] Add config.includeFiles (#277)
* add `includeFiles` support in `@now/node`

Co-Authored-By: sophearak <t.sophearak@gmail.com>
2019-03-20 14:07:06 -04:00
Sophearak Tha
b7f8b37ca6 [now-node-server] Add config.includeFiles (#302)
The main idea behind this is we want user be able to specific files in that should be include in the `handler` which later can be use in runtime.

For the most case, `ncc` be able to detect relevant files included in the `handler`. But the case that `ncc` not able to resolve, this option come in handy.

PR #277 does the same thing, but for `@now/node`.
2019-03-20 11:48:50 -04:00
Antonio Nuno Monteiro
13aa1b2d1c Publish now_lambda 0.1.3 (#287) 2019-03-20 10:56:12 -04:00
Nathan Rajlich
92437c075e [now-md] Remove console.log() call of Markdown file result (#303)
Not sure why this was here, but it's pretty noisey in the logs,
especially when executing this builder via `now dev`.
2019-03-20 09:09:16 -04:00
64 changed files with 1052 additions and 680 deletions

View File

@@ -20,12 +20,12 @@ jobs:
- run:
name: Bootstrapping
command: yarn bootstrap
- run:
name: Linting
command: yarn lint
- run:
name: Building
command: ./.circleci/build.sh
- run:
name: Linting
command: yarn lint
- run:
name: Tests
command: yarn test

View File

@@ -2,3 +2,6 @@
/node_modules/*
/**/node_modules/*
/packages/now-go/go/*
/packages/now-build-utils/dist/*
/packages/now-node/dist/*
/packages/now-node-bridge/*

View File

@@ -14,7 +14,8 @@
"bootstrap": "lerna bootstrap",
"publish-stable": "lerna version",
"publish-canary": "lerna version prerelease --preid canary",
"lint": "tsc && eslint .",
"build": "./.circleci/build.sh",
"lint": "eslint .",
"test": "jest --runInBand --verbose",
"lint-staged": "lint-staged"
},
@@ -43,7 +44,6 @@
"lint-staged": "^8.0.4",
"node-fetch": "^2.3.0",
"pre-commit": "^1.2.2",
"prettier": "^1.15.2",
"typescript": "^3.1.6"
"prettier": "^1.15.2"
}
}

1
packages/now-build-utils/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

View File

@@ -1,2 +1,3 @@
/src
/test
tmp

View File

@@ -1,33 +1 @@
const assert = require('assert');
const intoStream = require('into-stream');
class FileBlob {
constructor({ mode = 0o100644, data }) {
assert(typeof mode === 'number');
assert(typeof data === 'string' || Buffer.isBuffer(data));
this.type = 'FileBlob';
this.mode = mode;
this.data = data;
}
static async fromStream({ mode = 0o100644, stream }) {
assert(typeof mode === 'number');
assert(typeof stream.pipe === 'function'); // is-stream
const chunks = [];
await new Promise((resolve, reject) => {
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
stream.on('error', error => reject(error));
stream.on('end', () => resolve());
});
const data = Buffer.concat(chunks);
return new FileBlob({ mode, data });
}
toStream() {
return intoStream(this.data);
}
}
module.exports = FileBlob;
module.exports = require('./dist/index').FileBlob;

View File

@@ -1,100 +1 @@
const assert = require('assert');
const fs = require('fs-extra');
const multiStream = require('multistream');
const path = require('path');
const Sema = require('async-sema');
/** @typedef {{[filePath: string]: FileFsRef}} FsFiles */
const semaToPreventEMFILE = new Sema(30);
/**
* @constructor
* @argument {Object} options
* @argument {number} [options.mode=0o100644]
* @argument {string} options.fsPath
*/
class FileFsRef {
constructor({ mode = 0o100644, fsPath }) {
assert(typeof mode === 'number');
assert(typeof fsPath === 'string');
/** @type {string} */
this.type = 'FileFsRef';
/** @type {number} */
this.mode = mode;
/** @type {string} */
this.fsPath = fsPath;
}
/**
* Creates a `FileFsRef` with the correct `mode` from the file system.
*
* @argument {Object} options
* @argument {string} options.fsPath
* @returns {Promise<FileFsRef>}
*/
static async fromFsPath({ fsPath }) {
const { mode } = await fs.lstat(fsPath);
return new FileFsRef({ mode, fsPath });
}
/**
* @argument {Object} options
* @argument {number} [options.mode=0o100644]
* @argument {NodeJS.ReadableStream} options.stream
* @argument {string} options.fsPath
* @returns {Promise<FileFsRef>}
*/
static async fromStream({ mode = 0o100644, stream, fsPath }) {
assert(typeof mode === 'number');
assert(typeof stream.pipe === 'function'); // is-stream
assert(typeof fsPath === 'string');
await fs.mkdirp(path.dirname(fsPath));
await new Promise((resolve, reject) => {
const dest = fs.createWriteStream(fsPath);
stream.pipe(dest);
stream.on('error', reject);
dest.on('finish', resolve);
dest.on('error', reject);
});
await fs.chmod(fsPath, mode.toString(8).slice(-3));
return new FileFsRef({ mode, fsPath });
}
/**
* @returns {Promise<NodeJS.ReadableStream>}
*/
async toStreamAsync() {
await semaToPreventEMFILE.acquire();
const release = () => semaToPreventEMFILE.release();
const stream = fs.createReadStream(this.fsPath);
stream.on('close', release);
stream.on('error', release);
return stream;
}
/**
* @returns {NodeJS.ReadableStream}
*/
toStream() {
let flag;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
cb(null, stream);
})
.catch((error) => {
cb(error, null);
});
});
}
}
module.exports = FileFsRef;
module.exports = require('./dist/index').FileFsRef;

View File

@@ -1,96 +1 @@
const assert = require('assert');
const fetch = require('node-fetch');
const multiStream = require('multistream');
const retry = require('async-retry');
const Sema = require('async-sema');
/** @typedef {{[filePath: string]: FileRef}} Files */
const semaToDownloadFromS3 = new Sema(10);
class BailableError extends Error {
constructor(...args) {
super(...args);
/** @type {boolean} */
this.bail = false;
}
}
/**
* @constructor
* @argument {Object} options
* @argument {number} [options.mode=0o100644]
* @argument {string} options.digest
*/
class FileRef {
constructor({ mode = 0o100644, digest }) {
assert(typeof mode === 'number');
assert(typeof digest === 'string');
/** @type {string} */
this.type = 'FileRef';
/** @type {number} */
this.mode = mode;
/** @type {string} */
this.digest = digest;
}
/**
* @returns {Promise<NodeJS.ReadableStream>}
*/
async toStreamAsync() {
let url;
// sha:24be087eef9fac01d61b30a725c1a10d7b45a256
const digestParts = this.digest.split(':');
if (digestParts[0] === 'sha') {
// url = `https://s3.amazonaws.com/now-files/${digestParts[1]}`;
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`;
}
assert(url);
await semaToDownloadFromS3.acquire();
// console.time(`downloading ${url}`);
try {
return await retry(
async () => {
const resp = await fetch(url);
if (!resp.ok) {
const error = new BailableError(
`download: ${resp.status} ${resp.statusText} for ${url}`,
);
if (resp.status === 403) error.bail = true;
throw error;
}
return resp.body;
},
{ factor: 1, retries: 3 },
);
} finally {
// console.timeEnd(`downloading ${url}`);
semaToDownloadFromS3.release();
}
}
/**
* @returns {NodeJS.ReadableStream}
*/
toStream() {
let flag;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
cb(null, stream);
})
.catch((error) => {
cb(error, null);
});
});
}
}
module.exports = FileRef;
module.exports = require('./dist/index').FileRef;

View File

@@ -1,38 +1 @@
const path = require('path');
const FileFsRef = require('../file-fs-ref.js');
/** @typedef {import('../file-ref')} FileRef */
/** @typedef {import('../file-fs-ref')} FileFsRef */
/** @typedef {{[filePath: string]: FileRef|FileFsRef}} Files */
/** @typedef {{[filePath: string]: FileFsRef}|{}} DownloadedFiles */
/**
* @param {FileRef|FileFsRef} file
* @param {string} fsPath
* @returns {Promise<FileFsRef>}
*/
async function downloadFile(file, fsPath) {
const { mode } = file;
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
/**
* Download files to disk
* @argument {Files} files
* @argument {string} basePath
* @returns {Promise<DownloadedFiles>}
*/
module.exports = async function download(files, basePath) {
const files2 = {};
await Promise.all(
Object.keys(files).map(async (name) => {
const file = files[name];
const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
);
return files2;
};
module.exports = require('../dist/fs/download').default;

View File

@@ -1,10 +1 @@
const { join } = require('path');
const { tmpdir } = require('os');
const { mkdirp } = require('fs-extra');
module.exports = async function getWritableDirectory() {
const name = Math.floor(Math.random() * 0x7fffffff).toString(16);
const directory = join(tmpdir(), name);
await mkdirp(directory);
return directory;
};
module.exports = require('../dist/fs/get-writable-directory').default;

View File

@@ -1,67 +1 @@
const assert = require('assert');
const path = require('path');
const vanillaGlob = require('glob');
const FileFsRef = require('../file-fs-ref.js');
/** @typedef {import('fs').Stats} Stats */
/** @typedef {import('glob').IOptions} GlobOptions */
/** @typedef {import('../file-fs-ref').FsFiles|{}} GlobFiles */
/**
* @argument {string} pattern
* @argument {GlobOptions|string} opts
* @argument {string} [mountpoint]
* @returns {Promise<GlobFiles>}
*/
module.exports = function glob(pattern, opts = {}, mountpoint) {
return new Promise((resolve, reject) => {
/** @type {GlobOptions} */
let options;
if (typeof opts === 'string') {
options = { cwd: opts };
} else {
options = opts;
}
if (!options.cwd) {
throw new Error(
'Second argument (basePath) must be specified for names of resulting files',
);
}
if (!path.isAbsolute(options.cwd)) {
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
}
options.statCache = {};
options.stat = true;
options.dot = true;
// eslint-disable-next-line consistent-return
vanillaGlob(pattern, options, (error, files) => {
if (error) return reject(error);
resolve(
files.reduce((files2, relativePath) => {
const fsPath = path.join(options.cwd, relativePath);
/** @type {Stats|any} */
const stat = options.statCache[fsPath];
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
);
if (stat && stat.isFile()) {
let finalPath = relativePath;
if (mountpoint) finalPath = path.join(mountpoint, finalPath);
return {
...files2,
[finalPath]: new FileFsRef({ mode: stat.mode, fsPath }),
};
}
return files2;
}, {}),
);
});
});
};
module.exports = require('../dist/fs/glob').default;

View File

@@ -1,25 +1 @@
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
/** @typedef { import('@now/build-utils/file-fs-ref') } FileFsRef */
/** @typedef {{[filePath: string]: FileRef|FileFsRef}} Files */
/**
* @callback delegate
* @argument {string} name
* @returns {string}
*/
/**
* Rename files using delegate function
* @argument {Files} files
* @argument {delegate} delegate
* @returns {Files}
*/
module.exports = function rename(files, delegate) {
return Object.keys(files).reduce(
(newFiles, name) => ({
...newFiles,
[delegate(name)]: files[name],
}),
{},
);
};
module.exports = require('../dist/fs/rename').default;

View File

@@ -1,119 +1 @@
const assert = require('assert');
const fs = require('fs-extra');
const path = require('path');
const { spawn } = require('child_process');
function spawnAsync(command, args, cwd, opts = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: 'inherit', cwd, ...opts });
child.on('error', reject);
child.on('close', (code, signal) => (code !== 0
? reject(new Error(`Exited with ${code || signal}`))
: resolve()));
});
}
async function chmodPlusX(fsPath) {
const s = await fs.stat(fsPath);
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
if (s.mode === newMode) return;
const base8 = newMode.toString(8).slice(-3);
await fs.chmod(fsPath, base8);
}
async function runShellScript(fsPath) {
assert(path.isAbsolute(fsPath));
const destPath = path.dirname(fsPath);
await chmodPlusX(fsPath);
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
return true;
}
async function scanParentDirs(destPath, scriptName) {
assert(path.isAbsolute(destPath));
let hasScript = false;
let hasPackageLockJson = false;
let currentDestPath = destPath;
// eslint-disable-next-line no-constant-condition
while (true) {
const packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop
if (await fs.exists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
const packageJson = JSON.parse(await fs.readFile(packageJsonPath));
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName],
);
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.exists(
path.join(currentDestPath, 'package-lock.json'),
);
break;
}
const newDestPath = path.dirname(currentDestPath);
if (currentDestPath === newDestPath) break;
currentDestPath = newDestPath;
}
return { hasScript, hasPackageLockJson };
}
async function installDependencies(destPath, args = []) {
assert(path.isAbsolute(destPath));
let commandArgs = args;
console.log(`installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts = {
env: {
...process.env,
// This is a little hack to force `node-gyp` to build for the
// Node.js version that `@now/node` and `@now/node-server` use
npm_config_target: '8.10.0',
},
};
if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync('npm', ['install'].concat(commandArgs), destPath, opts);
await spawnAsync('npm', ['cache', 'clean', '--force'], destPath, opts);
} else {
await spawnAsync(
'yarn',
['--cwd', destPath].concat(commandArgs),
destPath,
opts,
);
await spawnAsync('yarn', ['cache', 'clean'], destPath, opts);
}
}
async function runPackageJsonScript(destPath, scriptName) {
assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs(
destPath,
scriptName,
);
if (!hasScript) return false;
if (hasPackageLockJson) {
console.log(`running "npm run ${scriptName}"`);
await spawnAsync('npm', ['run', scriptName], destPath);
} else {
console.log(`running "yarn run ${scriptName}"`);
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], destPath);
}
return true;
}
module.exports = {
runShellScript,
installDependencies,
runNpmInstall: installDependencies,
runPackageJsonScript,
};
module.exports = require('../dist/fs/run-user-scripts');

View File

@@ -1,4 +1 @@
const fastStreamToBuffer = require('fast-stream-to-buffer');
const { promisify } = require('util');
module.exports = promisify(fastStreamToBuffer);
module.exports = require('../dist/fs/stream-to-buffer').default;

View File

@@ -1,60 +1 @@
const assert = require('assert');
const Sema = require('async-sema');
const { ZipFile } = require('yazl');
const streamToBuffer = require('./fs/stream-to-buffer.js');
class Lambda {
constructor({
zipBuffer, handler, runtime, environment,
}) {
this.type = 'Lambda';
this.zipBuffer = zipBuffer;
this.handler = handler;
this.runtime = runtime;
this.environment = environment;
}
}
const sema = new Sema(10);
const mtime = new Date(1540000000000);
async function createLambda({
files, handler, runtime, environment = {},
}) {
assert(typeof files === 'object', '"files" must be an object');
assert(typeof handler === 'string', '"handler" is not a string');
assert(typeof runtime === 'string', '"runtime" is not a string');
assert(typeof environment === 'object', '"environment" is not an object');
await sema.acquire();
try {
const zipFile = new ZipFile();
const zipBuffer = await new Promise((resolve, reject) => {
Object.keys(files)
.sort()
.forEach((name) => {
const file = files[name];
const stream = file.toStream();
stream.on('error', reject);
zipFile.addReadStream(stream, name, { mode: file.mode, mtime });
});
zipFile.end();
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
});
return new Lambda({
zipBuffer,
handler,
runtime,
environment,
});
} finally {
sema.release();
}
}
module.exports = {
Lambda,
createLambda,
};
module.exports = require('./dist/index');

View File

@@ -1,7 +1,9 @@
{
"name": "@now/build-utils",
"version": "0.4.37-canary.1",
"version": "0.4.37-canary.3",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",
@@ -10,16 +12,26 @@
"dependencies": {
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"fast-stream-to-buffer": "1.0.0",
"end-of-stream": "^1.4.1",
"fs-extra": "7.0.0",
"glob": "7.1.3",
"into-stream": "4.0.0",
"into-stream": "5.0.0",
"memory-fs": "0.4.1",
"multistream": "2.1.1",
"node-fetch": "2.2.0",
"yazl": "2.4.3"
},
"scripts": {
"test": "jest"
"build": "tsc",
"test": "tsc && jest",
"prepublish": "tsc"
},
"devDependencies": {
"@types/async-retry": "^1.2.1",
"@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.1.6",
"@types/yazl": "^2.4.1",
"typescript": "3.3.4000"
}
}

View File

@@ -0,0 +1,27 @@
import path from 'path';
import FileFsRef from './file-fs-ref';
import { File, Files } from './types';
export interface DownloadedFiles {
[filePath: string]: FileFsRef
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
export default async function download(files: Files, basePath: string): Promise<DownloadedFiles> {
const files2: DownloadedFiles = {};
await Promise.all(
Object.keys(files).map(async (name) => {
const file = files[name];
const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
);
return files2;
};

View File

@@ -0,0 +1,46 @@
import assert from 'assert';
import intoStream from 'into-stream';
import { File } from './types';
interface FileBlobOptions {
mode?: number;
data: string | Buffer;
}
interface FromStreamOptions {
mode?: number;
stream: NodeJS.ReadableStream;
}
export default class FileBlob implements File {
public type: string;
public mode: number;
public data: string | Buffer;
constructor({ mode = 0o100644, data }: FileBlobOptions) {
assert(typeof mode === 'number');
assert(typeof data === 'string' || Buffer.isBuffer(data));
this.type = 'FileBlob';
this.mode = mode;
this.data = data;
}
static async fromStream({ mode = 0o100644, stream }: FromStreamOptions) {
assert(typeof mode === 'number');
assert(typeof stream.pipe === 'function'); // is-stream
const chunks: Buffer[] = [];
await new Promise<void>((resolve, reject) => {
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
stream.on('error', error => reject(error));
stream.on('end', () => resolve());
});
const data = Buffer.concat(chunks);
return new FileBlob({ mode, data });
}
toStream(): NodeJS.ReadableStream {
return intoStream(this.data);
}
}

View File

@@ -0,0 +1,89 @@
import assert from 'assert';
import fs from 'fs-extra';
import multiStream from 'multistream';
import path from 'path';
import Sema from 'async-sema';
import { File } from './types';
const semaToPreventEMFILE = new Sema(30);
interface FileFsRefOptions {
mode?: number;
fsPath: string;
}
interface FromOptions {
fsPath: string;
}
interface FromStreamOptions {
mode: number;
stream: NodeJS.ReadableStream;
fsPath: string;
}
class FileFsRef implements File {
public type: string;
public mode: number;
public fsPath: string;
constructor({ mode = 0o100644, fsPath }: FileFsRefOptions) {
assert(typeof mode === 'number');
assert(typeof fsPath === 'string');
this.type = 'FileFsRef';
this.mode = mode;
this.fsPath = fsPath;
}
static async fromFsPath({ fsPath }: FromOptions): Promise<FileFsRef> {
const { mode } = await fs.lstat(fsPath);
return new FileFsRef({ mode, fsPath });
}
static async fromStream({ mode = 0o100644, stream, fsPath }: FromStreamOptions): Promise<FileFsRef> {
assert(typeof mode === 'number');
assert(typeof stream.pipe === 'function'); // is-stream
assert(typeof fsPath === 'string');
await fs.mkdirp(path.dirname(fsPath));
await new Promise<void>((resolve, reject) => {
const dest = fs.createWriteStream(fsPath);
stream.pipe(dest);
stream.on('error', reject);
dest.on('finish', resolve);
dest.on('error', reject);
});
await fs.chmod(fsPath, mode.toString(8).slice(-3));
return new FileFsRef({ mode, fsPath });
}
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
await semaToPreventEMFILE.acquire();
const release = () => semaToPreventEMFILE.release();
const stream = fs.createReadStream(this.fsPath);
stream.on('close', release);
stream.on('error', release);
return stream;
}
toStream(): NodeJS.ReadableStream {
let flag = false;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
cb(null, stream);
})
.catch((error) => {
cb(error, null);
});
});
}
}
export = FileFsRef;

View File

@@ -0,0 +1,88 @@
import assert from 'assert';
import fetch from 'node-fetch';
import multiStream from 'multistream';
import retry from 'async-retry';
import Sema from 'async-sema';
import { File } from './types';
interface FileRefOptions {
mode?: number;
digest: string;
}
const semaToDownloadFromS3 = new Sema(10);
class BailableError extends Error {
public bail: boolean;
constructor(...args: string[]) {
super(...args);
this.bail = false;
}
}
export default class FileRef implements File {
public type: string;
public mode: number;
public digest: string;
constructor({ mode = 0o100644, digest }: FileRefOptions) {
assert(typeof mode === 'number');
assert(typeof digest === 'string');
this.type = 'FileRef';
this.mode = mode;
this.digest = digest;
}
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
let url = '';
// sha:24be087eef9fac01d61b30a725c1a10d7b45a256
const digestParts = this.digest.split(':');
if (digestParts[0] === 'sha') {
// url = `https://s3.amazonaws.com/now-files/${digestParts[1]}`;
url = `https://dmmcy0pwk6bqi.cloudfront.net/${digestParts[1]}`;
} else {
throw new Error('Expected digest to be sha');
}
await semaToDownloadFromS3.acquire();
// console.time(`downloading ${url}`);
try {
return await retry(
async () => {
const resp = await fetch(url);
if (!resp.ok) {
const error = new BailableError(
`download: ${resp.status} ${resp.statusText} for ${url}`,
);
if (resp.status === 403) error.bail = true;
throw error;
}
return resp.body;
},
{ factor: 1, retries: 3 },
);
} finally {
// console.timeEnd(`downloading ${url}`);
semaToDownloadFromS3.release();
}
}
toStream(): NodeJS.ReadableStream {
let flag = false;
// eslint-disable-next-line consistent-return
return multiStream((cb) => {
if (flag) return cb(null, null);
flag = true;
this.toStreamAsync()
.then((stream) => {
cb(null, stream);
})
.catch((error) => {
cb(error, null);
});
});
}
}

View File

@@ -0,0 +1,27 @@
import path from 'path';
import FileFsRef from '../file-fs-ref';
import { File, Files } from '../types';
export interface DownloadedFiles {
[filePath: string]: FileFsRef
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
export default async function download(files: Files, basePath: string): Promise<DownloadedFiles> {
const files2: DownloadedFiles = {};
await Promise.all(
Object.keys(files).map(async (name) => {
const file = files[name];
const fsPath = path.join(basePath, name);
files2[name] = await downloadFile(file, fsPath);
}),
);
return files2;
};

View File

@@ -0,0 +1,10 @@
import { join } from 'path';
import { tmpdir } from 'os';
import { mkdirp } from 'fs-extra';
export default async function getWritableDirectory() {
const name = Math.floor(Math.random() * 0x7fffffff).toString(16);
const directory = join(tmpdir(), name);
await mkdirp(directory);
return directory;
}

View File

@@ -0,0 +1,61 @@
import assert from 'assert';
import path from 'path';
import vanillaGlob from 'glob';
import FileFsRef from '../file-fs-ref';
type GlobOptions = import('glob').IOptions;
interface FsFiles {
[filePath: string]: FileFsRef
}
export default function glob(pattern: string, opts: GlobOptions | string, mountpoint?: string): Promise<FsFiles> {
return new Promise<FsFiles>((resolve, reject) => {
let options: GlobOptions;
if (typeof opts === 'string') {
options = { cwd: opts };
} else {
options = opts;
}
if (!options.cwd) {
throw new Error(
'Second argument (basePath) must be specified for names of resulting files',
);
}
if (!path.isAbsolute(options.cwd)) {
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
}
options.statCache = {};
options.stat = true;
options.dot = true;
// eslint-disable-next-line consistent-return
vanillaGlob(pattern, options, (error, files) => {
if (error) return reject(error);
resolve(
files.reduce<FsFiles>((files2, relativePath) => {
const fsPath = path.join(options.cwd!, relativePath);
const stat = options.statCache![fsPath] as import('fs').Stats;
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`,
);
if (stat && stat.isFile()) {
let finalPath = relativePath;
if (mountpoint) finalPath = path.join(mountpoint, finalPath);
return {
...files2,
[finalPath]: new FileFsRef({ mode: stat.mode, fsPath }),
};
}
return files2;
}, {}),
);
});
});
};

View File

@@ -0,0 +1,12 @@
import { Files } from '../types';
type Delegate = (name: string) => string;
export default function rename(files: Files, delegate: Delegate): Files {
return Object.keys(files).reduce(
(newFiles, name) => ({
...newFiles,
[delegate(name)]: files[name],
}),
{},
);
}

View File

@@ -0,0 +1,114 @@
import assert from 'assert';
import fs from 'fs-extra';
import path from 'path';
import { spawn, SpawnOptions } from 'child_process';
function spawnAsync(command: string, args: string[], cwd: string, opts: SpawnOptions = {}) {
return new Promise<void>((resolve, reject) => {
const child = spawn(command, args, { stdio: 'inherit', cwd, ...opts });
child.on('error', reject);
child.on('close', (code, signal) => (code !== 0
? reject(new Error(`Exited with ${code || signal}`))
: resolve()));
});
}
async function chmodPlusX(fsPath: string) {
const s = await fs.stat(fsPath);
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
if (s.mode === newMode) return;
const base8 = newMode.toString(8).slice(-3);
await fs.chmod(fsPath, base8);
}
export async function runShellScript(fsPath: string) {
assert(path.isAbsolute(fsPath));
const destPath = path.dirname(fsPath);
await chmodPlusX(fsPath);
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
return true;
}
async function scanParentDirs(destPath: string, scriptName?: string) {
assert(path.isAbsolute(destPath));
let hasScript = false;
let hasPackageLockJson = false;
let currentDestPath = destPath;
// eslint-disable-next-line no-constant-condition
while (true) {
const packageJsonPath = path.join(currentDestPath, 'package.json');
// 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'));
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName],
);
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.pathExists(
path.join(currentDestPath, 'package-lock.json'),
);
break;
}
const newDestPath = path.dirname(currentDestPath);
if (currentDestPath === newDestPath) break;
currentDestPath = newDestPath;
}
return { hasScript, hasPackageLockJson };
}
export async function installDependencies(destPath: string, args: string[] = []) {
assert(path.isAbsolute(destPath));
let commandArgs = args;
console.log(`installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts = {
env: {
...process.env,
// This is a little hack to force `node-gyp` to build for the
// Node.js version that `@now/node` and `@now/node-server` use
npm_config_target: '8.10.0',
},
};
if (hasPackageLockJson) {
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync('npm', ['install'].concat(commandArgs), destPath, opts);
await spawnAsync('npm', ['cache', 'clean', '--force'], destPath, opts);
} else {
await spawnAsync(
'yarn',
['--cwd', destPath].concat(commandArgs),
destPath,
opts,
);
await spawnAsync('yarn', ['cache', 'clean'], destPath, opts);
}
}
export async function runPackageJsonScript(destPath: string, scriptName: string) {
assert(path.isAbsolute(destPath));
const { hasScript, hasPackageLockJson } = await scanParentDirs(
destPath,
scriptName,
);
if (!hasScript) return false;
if (hasPackageLockJson) {
console.log(`running "npm run ${scriptName}"`);
await spawnAsync('npm', ['run', scriptName], destPath);
} else {
console.log(`running "yarn run ${scriptName}"`);
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], destPath);
}
return true;
}
export const runNpmInstall = installDependencies;

View File

@@ -0,0 +1,26 @@
import eos from 'end-of-stream';
export default function streamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const buffers: Buffer[] = [];
stream.on('data', buffers.push.bind(buffers))
eos(stream, (err) => {
if (err) {
reject(err);
return;
}
switch (buffers.length) {
case 0:
resolve(Buffer.allocUnsafe(0));
break;
case 1:
resolve(buffers[0]);
break;
default:
resolve(Buffer.concat(buffers));
}
});
});
}

View File

@@ -0,0 +1,31 @@
import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import { File, Files, AnalyzeOptions, BuildOptions, PrepareCacheOptions } from './types';
import { Lambda, createLambda } from './lambda';
import download from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory'
import glob from './fs/glob';
import rename from './fs/rename';
import { installDependencies, runPackageJsonScript, runNpmInstall, runShellScript } from './fs/run-user-scripts';
import streamToBuffer from './fs/stream-to-buffer';
export {
FileBlob,
FileFsRef,
FileRef,
Files,
File,
Lambda,
createLambda,
download,
getWriteableDirectory,
glob,
rename,
installDependencies, runPackageJsonScript, runNpmInstall, runShellScript,
streamToBuffer,
AnalyzeOptions,
BuildOptions,
PrepareCacheOptions,
};

View File

@@ -0,0 +1,80 @@
import assert from 'assert';
import Sema from 'async-sema';
import { ZipFile } from 'yazl';
import streamToBuffer from './fs/stream-to-buffer';
import { Files } from './types';
interface Environment {
[key: string]: string;
}
interface LambdaOptions {
zipBuffer: Buffer;
handler: string;
runtime: string;
environment: Environment;
}
interface CreateLambdaOptions {
files: Files;
handler: string;
runtime: string;
environment?: Environment;
}
export class Lambda {
public type: string;
public zipBuffer: Buffer;
public handler: string;
public runtime: string;
public environment: Environment;
constructor({
zipBuffer, handler, runtime, environment,
}: LambdaOptions) {
this.type = 'Lambda';
this.zipBuffer = zipBuffer;
this.handler = handler;
this.runtime = runtime;
this.environment = environment;
}
}
const sema = new Sema(10);
const mtime = new Date(1540000000000);
export async function createLambda({
files, handler, runtime, environment = {},
}: CreateLambdaOptions): Promise<Lambda> {
assert(typeof files === 'object', '"files" must be an object');
assert(typeof handler === 'string', '"handler" is not a string');
assert(typeof runtime === 'string', '"runtime" is not a string');
assert(typeof environment === 'object', '"environment" is not an object');
await sema.acquire();
try {
const zipFile = new ZipFile();
const zipBuffer = await new Promise<Buffer>((resolve, reject) => {
Object.keys(files)
.sort()
.forEach((name) => {
const file = files[name];
const stream = file.toStream() as import('stream').Readable;
stream.on('error', reject);
zipFile.addReadStream(stream, name, { mode: file.mode, mtime });
});
zipFile.end();
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
});
return new Lambda({
zipBuffer,
handler,
runtime,
environment,
});
} finally {
sema.release();
}
}

View File

@@ -0,0 +1,101 @@
export interface File {
type: string;
mode: number;
toStream: () => NodeJS.ReadableStream;
}
export interface Files {
[filePath: string]: File
}
export interface Config {
[key: string]: string
}
export interface AnalyzeOptions {
/**
* All source files of the project
*/
files: Files;
/**
* Name of entrypoint file for this particular build job. Value
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
* `entrypoint` is always a discrete file and never a glob, since globs are
* expanded into separate builds at deployment time.
*/
entrypoint: string;
/**
* A writable temporary directory where you are encouraged to perform your
* build process. This directory will be populated with the restored cache.
*/
workPath: string;
/**
* An arbitrary object passed by the user in the build definition defined
* in `now.json`.
*/
config: Config;
}
export interface BuildOptions {
/**
* All source files of the project
*/
files: Files;
/**
* Name of entrypoint file for this particular build job. Value
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
* `entrypoint` is always a discrete file and never a glob, since globs are
* expanded into separate builds at deployment time.
*/
entrypoint: string;
/**
* A writable temporary directory where you are encouraged to perform your
* build process. This directory will be populated with the restored cache.
*/
workPath: string;
/**
* An arbitrary object passed by the user in the build definition defined
* in `now.json`.
*/
config: Config;
}
export interface PrepareCacheOptions {
/**
* All source files of the project
*/
files: Files;
/**
* Name of entrypoint file for this particular build job. Value
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
* `entrypoint` is always a discrete file and never a glob, since globs are
* expanded into separate builds at deployment time.
*/
entrypoint: string;
/**
* A writable temporary directory where you are encouraged to perform your
* build process.
*/
workPath: string;
/**
* A writable temporary directory where you can build a cache to use for
* the next run.
*/
cachePath: string;
/**
* An arbitrary object passed by the user in the build definition defined
* in `now.json`.
*/
config: Config;
}

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "./dist",
"types": ["node"],
"strict": true,
"target": "esnext"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@@ -68,9 +68,11 @@ async function build({ files, entrypoint }) {
);
if (!isGoModExist) {
try {
go('mod', 'init', packageName);
const defaultGoModContent = `module ${packageName}`;
await writeFile(join(entrypointDirname, 'go.mod'), defaultGoModContent);
} catch (err) {
console.log(`failed to \`go mod init ${packageName}\``);
console.log(`failed to create default go.mod for ${packageName}`);
throw err;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/go",
"version": "0.3.1-canary.1",
"version": "0.3.1-canary.2",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -11,7 +11,6 @@ const defaultOptions = {
removeRedundantAttributes: true,
useShortDoctype: true,
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
collapseBooleanAttributes: true,
caseSensitive: true,
};

View File

@@ -1,6 +1,6 @@
{
"name": "@now/html-minifier",
"version": "1.0.8-canary.0",
"version": "1.0.8-canary.1",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -34,8 +34,6 @@ exports.build = async ({ files, entrypoint, config }) => {
stream: stream.pipe(unifiedStream(processor)),
});
console.log(result.data.toString());
const replacedEntrypoint = entrypoint.replace(/\.[^.]+$/, '.html');
return { [replacedEntrypoint]: result };

View File

@@ -1,6 +1,6 @@
{
"name": "@now/md",
"version": "0.4.10-canary.1",
"version": "0.4.10-canary.2",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,13 +1,13 @@
const { createLambda } = require('@now/build-utils/lambda.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 FileFsRef = require('@now/build-utils/file-fs-ref.js'); // eslint-disable-line import/no-extraneous-dependencies
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
const FileFsRef = require('@now/build-utils/file-fs-ref'); // eslint-disable-line import/no-extraneous-dependencies
const FileBlob = require('@now/build-utils/file-blob'); // eslint-disable-line import/no-extraneous-dependencies
const path = require('path');
const {
runNpmInstall,
runPackageJsonScript,
} = require('@now/build-utils/fs/run-user-scripts.js'); // eslint-disable-line import/no-extraneous-dependencies
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
} = require('@now/build-utils/fs/run-user-scripts'); // eslint-disable-line import/no-extraneous-dependencies
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
const {
readFile,
writeFile,
@@ -391,7 +391,10 @@ exports.prepareCache = async ({ cachePath, workPath, entrypoint }) => {
const cacheEntrypoint = path.relative(cachePath, cacheEntryPath);
return {
...(await glob(
path.join(cacheEntrypoint, 'node_modules/{**,!.*,.yarn*,.cache/next-minifier/**}'),
path.join(
cacheEntrypoint,
'node_modules/{**,!.*,.yarn*,.cache/next-minifier/**}',
),
cachePath,
)),
...(await glob(path.join(cacheEntrypoint, 'package-lock.json'), cachePath)),

View File

@@ -1,8 +1,8 @@
process.env.NODE_ENV = 'production';
const { Server } = require('http');
const { Bridge } = require('./now__bridge.js');
const page = require('./page.js');
const { Bridge } = require('./now__bridge');
const page = require('./page');
const server = new Server(page.render);
const bridge = new Bridge(server);

View File

@@ -1,7 +1,7 @@
const { Server } = require('http');
const next = require('next-server');
const url = require('url');
const { Bridge } = require('./now__bridge.js');
const { Bridge } = require('./now__bridge');
process.env.NODE_ENV = 'production';

View File

@@ -1,7 +1,10 @@
{
"name": "@now/next",
"version": "0.1.3-canary.1",
"version": "0.1.3-canary.2",
"license": "MIT",
"scripts": {
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"lib": ["es2017"],
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false,
"types": ["node"],
"esModuleInterop": true
},
"include": [
"./"
],
"exclude": [
"./launcher.js",
"./legacy-launcher.js"
]
}

View File

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

View File

@@ -1,6 +1,8 @@
{
"compilerOptions": {
"target": "es6",
"esModuleInterop": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs",
"outDir": ".",
"strict": true,

View File

@@ -61,11 +61,33 @@ async function downloadInstallAndBundle(
return [downloadedFiles, userPath, nccPath, entrypointFsDirname];
}
async function compile(workNccPath, downloadedFiles, entrypoint) {
async function compile(workNccPath, downloadedFiles, entrypoint, config) {
const input = downloadedFiles[entrypoint].fsPath;
const inputDir = path.dirname(input);
const ncc = require(path.join(workNccPath, 'node_modules/@zeit/ncc'));
const { code, assets } = await ncc(input, { sourceMap: true });
if (config && config.includeFiles) {
// eslint-disable-next-line no-restricted-syntax
for (const pattern of config.includeFiles) {
// eslint-disable-next-line no-await-in-loop
const files = await glob(pattern, inputDir);
// eslint-disable-next-line no-restricted-syntax
for (const assetName of Object.keys(files)) {
const stream = files[assetName].toStream();
const { mode } = files[assetName];
// eslint-disable-next-line no-await-in-loop
const { data } = await FileBlob.fromStream({ stream });
assets[assetName] = {
source: data,
permissions: mode,
};
}
}
}
const preparedFiles = {};
const blob = new FileBlob({ data: code });
// move all user code to 'user' subdirectory
@@ -115,7 +137,12 @@ exports.build = async ({
preparedFiles = rename(preparedFiles, name => path.join('user', name));
} else {
console.log('compiling entrypoint with ncc...');
preparedFiles = await compile(workNccPath, downloadedFiles, entrypoint);
preparedFiles = await compile(
workNccPath,
downloadedFiles,
entrypoint,
config,
);
}
const launcherPath = path.join(__dirname, 'launcher.js');

View File

@@ -14,6 +14,18 @@ if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production';
}
// PLACEHOLDER
try {
// PLACEHOLDER
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error(
'Did you forget to add it to "dependencies" in `package.json`?',
);
process.exit(1);
} else {
throw err;
}
}
exports.launcher = bridge.launcher;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node-server",
"version": "0.5.2-canary.2",
"version": "0.5.2-canary.3",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,7 @@
"directory": "packages/now-node-server"
},
"dependencies": {
"@now/node-bridge": "^1.0.1-canary.0",
"@now/node-bridge": "^1.0.1-canary.1",
"fs-extra": "7.0.1"
},
"scripts": {

View File

@@ -0,0 +1,6 @@
const express = require('express');
const app = express();
app.use(express.static('templates'));
app.listen();

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@now/node-server",
"config": {
"includeFiles": [
"templates/**"
]
}
}
],
"probes": [
{
"path": "/",
"mustContain": "Hello Now!"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"express": "^4.16.4"
}
}

View File

@@ -0,0 +1 @@
Hello Now!

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "0.5.2-canary.4",
"version": "0.5.2-canary.5",
"license": "MIT",
"main": "./dist/index",
"repository": {
@@ -9,7 +9,7 @@
"directory": "packages/now-node"
},
"dependencies": {
"@now/node-bridge": "^1.0.1-canary.0",
"@now/node-bridge": "^1.0.1-canary.1",
"fs-extra": "7.0.1"
},
"scripts": {

View File

@@ -1,34 +1,35 @@
import { join, dirname } from 'path';
import { remove, readFile } from 'fs-extra';
import * as glob from '@now/build-utils/fs/glob.js';
import * as download from '@now/build-utils/fs/download.js';
import * as FileBlob from '@now/build-utils/file-blob.js';
import * as FileFsRef from '@now/build-utils/file-fs-ref.js';
import { createLambda } from '@now/build-utils/lambda.js';
import {
glob,
download,
FileBlob,
FileFsRef,
Files,
createLambda,
runNpmInstall,
runPackageJsonScript
} from '@now/build-utils/fs/run-user-scripts.js';
runPackageJsonScript,
PrepareCacheOptions,
BuildOptions,
} from '@now/build-utils';
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
/** @typedef {{[filePath: string]: FileRef}} Files */
interface CompilerConfig {
includeFiles?: string[]
}
/**
* @typedef {Object} BuildParamsType
* @property {Files} files - Files object
* @property {string} entrypoint - Entrypoint specified for the builder
* @property {string} workPath - Working directory for this build
*/
interface DownloadOptions {
files: Files,
entrypoint: string;
workPath: string;
npmArguments?: string[];
}
/**
* @param {BuildParamsType} buildParams
* @param {Object} [options]
* @param {string[]} [options.npmArguments]
*/
async function downloadInstallAndBundle(
{ files, entrypoint, workPath },
{ npmArguments = [] } = {}
) {
async function downloadInstallAndBundle({
files,
entrypoint,
workPath,
npmArguments = []
}: DownloadOptions) {
const userPath = join(workPath, 'user');
const nccPath = join(workPath, 'ncc');
@@ -56,14 +57,33 @@ async function downloadInstallAndBundle(
console.log('installing dependencies for ncc...');
await runNpmInstall(nccPath, npmArguments);
return [downloadedFiles, nccPath, entrypointFsDirname];
const entrypointPath = downloadedFiles[entrypoint].fsPath;
return { entrypointPath, workNccPath: nccPath, entrypointFsDirname };
}
async function compile(workNccPath: string, downloadedFiles, entrypoint: string) {
const input = downloadedFiles[entrypoint].fsPath;
async function compile(workNccPath: string, entrypointPath: string, entrypoint: string, config: CompilerConfig) {
const input = entrypointPath;
const inputDir = dirname(input);
const ncc = require(join(workNccPath, 'node_modules/@zeit/ncc'));
const { code, assets } = await ncc(input);
if (config && config.includeFiles) {
for (const pattern of config.includeFiles) {
const files = await glob(pattern, inputDir);
for (const assetName of Object.keys(files)) {
const stream = files[assetName].toStream();
const { mode } = files[assetName];
const { data } = await FileBlob.fromStream({ stream });
assets[assetName] = {
'source': data,
'permissions': mode
};
}
}
}
const preparedFiles = {};
const blob = new FileBlob({ data: code });
// move all user code to 'user' subdirectory
@@ -82,25 +102,20 @@ export const config = {
maxLambdaSize: '5mb'
};
/**
* @param {BuildParamsType} buildParams
* @returns {Promise<Files>}
*/
export async function build({ files, entrypoint, workPath }) {
const [
downloadedFiles,
export async function build({ files, entrypoint, workPath, config }: BuildOptions) {
const {
entrypointPath,
workNccPath,
entrypointFsDirname
] = await downloadInstallAndBundle(
{ files, entrypoint, workPath },
{ npmArguments: ['--prefer-offline'] }
} = await downloadInstallAndBundle(
{ files, entrypoint, workPath, npmArguments: ['--prefer-offline'] }
);
console.log('running user script...');
await runPackageJsonScript(entrypointFsDirname, 'now-build');
console.log('compiling entrypoint with ncc...');
const preparedFiles = await compile(workNccPath, downloadedFiles, entrypoint);
const preparedFiles = await compile(workNccPath, entrypointPath, entrypoint, config);
const launcherPath = join(__dirname, 'launcher.js');
let launcherData = await readFile(launcherPath, 'utf8');
@@ -127,7 +142,7 @@ export async function build({ files, entrypoint, workPath }) {
return { [entrypoint]: lambda };
}
export async function prepareCache({ files, entrypoint, workPath, cachePath }) {
export async function prepareCache({ files, entrypoint, workPath, cachePath }: PrepareCacheOptions) {
await remove(workPath);
await downloadInstallAndBundle({ files, entrypoint, workPath: cachePath });

View File

@@ -7,7 +7,17 @@ if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'production';
}
try {
// PLACEHOLDER
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error('Did you forget to add it to "dependencies" in `package.json`?');
process.exit(1);
} else {
throw err;
}
}
const server = new Server(listener);
const bridge = new Bridge(server);

View File

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

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@now/node",
"config": {
"includeFiles": [
"templates/**"
]
}
}
],
"probes": [
{
"path": "/",
"mustContain": "hello Now!"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"edge.js": "^1.1.4"
}
}

View File

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

View File

@@ -1,6 +1,8 @@
{
"compilerOptions": {
"target": "es6",
"esModuleInterop": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs",
"outDir": "dist",
"sourceMap": false,

View File

@@ -448,7 +448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "now_lambda"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@@ -1,6 +1,6 @@
[package]
name = "now_lambda"
version = "0.1.2"
version = "0.1.3"
authors = ["Antonio Nuno Monteiro <anmonteiro@gmail.com>"]
edition = "2018"
description = "Rust bindings for Now.sh Lambdas"

View File

@@ -1,6 +1,6 @@
{
"name": "@now/rust",
"version": "0.1.2-canary.0",
"version": "0.1.2-canary.1",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -20,7 +20,12 @@ async function nowDeploy (bodies, randomness) {
version: 2,
public: true,
env: { ...nowJson.env, RANDOMNESS_ENV_VAR: randomness },
build: { env: { ...(nowJson.build || {}).env, RANDOMNESS_BUILD_ENV_VAR: randomness } },
build: {
env: {
...(nowJson.build || {}).env,
RANDOMNESS_BUILD_ENV_VAR: randomness
}
},
name: 'test',
files,
builds: nowJson.builds,
@@ -31,10 +36,7 @@ async function nowDeploy (bodies, randomness) {
console.log(`posting ${files.length} files`);
for (const { file: filename } of files) {
await filePost(
bodies[filename],
digestOfFile(bodies[filename])
);
await filePost(bodies[filename], digestOfFile(bodies[filename]));
}
let deploymentId;
@@ -119,8 +121,7 @@ async function fetchWithAuth (url, opts = {}) {
if (NOW_TOKEN) {
token = NOW_TOKEN;
} else
if (NOW_TOKEN_FACTORY_URL) {
} else if (NOW_TOKEN_FACTORY_URL) {
const resp = await fetch(NOW_TOKEN_FACTORY_URL);
token = (await resp.json()).token;
} else {
@@ -151,6 +152,8 @@ async function fetchApi (url, opts = {}) {
opts.headers.Accept = 'application/json';
}
opts.headers['x-now-trace-priority'] = '1';
return await fetch(urlWithHost, opts);
}

View File

@@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"lib": ["es2017"],
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false,
"types": ["node"],
"esModuleInterop": true
},
"include": [
"./packages/now-node/index.js",
"./packages/now-build-utils/file-ref.js",
"./packages/now-build-utils/file-fs-ref.js",
"./packages/now-build-utils/fs/rename.js",
"./packages/now-build-utils/fs/download.js",
"./packages/now-build-utils/fs/glob.js",
"./packages/now-next"
],
"exclude": [
"./packages/now-next/launcher.js",
"./packages/now-next/legacy-launcher.js"
]
}

View File

@@ -740,11 +740,23 @@
dependencies:
any-observable "^0.3.0"
"@types/async-retry@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.2.1.tgz#fa9ac165907a8ee78f4924f4e393b656c65b5bb4"
integrity sha512-yMQ6CVgICWtyFNBqJT3zqOc+TnqqEPLo4nKJNPFwcialiylil38Ie6q1ENeFTjvaLOkVim9K5LisHgAKJWidGQ==
"@types/aws-lambda@8.10.19":
version "8.10.19"
resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.19.tgz#913a8016a4599d262960d97cb11faf7e963ec0e1"
integrity sha512-dEhQow/1awGGIf/unEpb97vsTtnQ3qRPAhSmZZcXKzs4nOVbIuWo5LCCzOYdSIkGkkoFXVvc8pBaSVKRYIFUBA==
"@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"
integrity sha512-d0FD2A4vpFI8wyQeQbr9VDVKtA1PmeGO3Ntn+6j626QTtAQ9HSqWFACP7rTHaV2cspVhLijl00Vvkf/U2UZGWA==
dependencies:
"@types/node" "*"
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
@@ -757,6 +769,13 @@
dependencies:
"@types/node" "*"
"@types/fs-extra@^5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b"
integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==
dependencies:
"@types/node" "*"
"@types/glob@^7.1.1":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
@@ -778,6 +797,13 @@
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.1.6":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.1.6.tgz#4326288b49f352a142f03c63526ebce0f4c50877"
integrity sha512-Hv1jgh3pfpUEl2F2mqUd1AfLSk1YbUCeBJFaP36t7esAO617dErqdxWb5cdG2NfJGOofkmBW36fdx0dVewxDRg==
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"
@@ -788,6 +814,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14"
integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==
"@types/yazl@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/yazl/-/yazl-2.4.1.tgz#0441a6ee151bf8be9307a2318b89df50f174ea00"
integrity sha512-uTgQOl6gCKZ6ys5x2BmnNCd/Em8TqCltjPtyHFc1mz8Q6/+Na7yWnoPgCPhsl44M7S6MfaL6spL6pUM1c7NcDg==
dependencies:
"@types/node" "*"
"@zeit/best@0.4.3":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
@@ -3065,13 +3098,6 @@ fast-levenshtein@~2.0.4:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-stream-to-buffer@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-stream-to-buffer/-/fast-stream-to-buffer-1.0.0.tgz#793340cc753e7ec9c7fb6d57a53a0b911cb0f588"
integrity sha512-bI/544WUQlD2iXBibQbOMSmG07Hay7YrpXlKaeGTPT7H7pC0eitt3usak5vUwEvCGK/O7rUAM3iyQValGU22TQ==
dependencies:
end-of-stream "^1.4.1"
fastcgi-client@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/fastcgi-client/-/fastcgi-client-0.0.1.tgz#1046d42ff2cee2a9ac03fea04695b3ef7311861c"
@@ -4105,10 +4131,10 @@ inspect-with-kind@^1.0.4:
dependencies:
kind-of "^6.0.2"
into-stream@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-4.0.0.tgz#ef10ee2ffb6f78af34c93194bbdc36c35f7d8a9d"
integrity sha512-i29KNyE5r0Y/UQzcQ0IbZO1MYJ53Jn0EcFRZPj5FzWKYH17kDFEOwuA+3jroymOI06SW1dEDnly9A1CAreC5dg==
into-stream@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-5.0.0.tgz#690569d7806b29d7cbd496cb05972fbe725b42a5"
integrity sha512-VcdJDRK7+vZrcGCdklXy9Zu6lwh2BFVwxCUhqYwolNYAsJE5og3aY4PR+03Hup8pwKV6JhvQ4dxRMOHUgrutdg==
dependencies:
from2 "^2.1.1"
p-is-promise "^2.0.0"
@@ -8793,10 +8819,10 @@ typescript@3.3.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3.tgz#f1657fc7daa27e1a8930758ace9ae8da31403221"
integrity sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A==
typescript@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
typescript@3.3.4000:
version "3.3.4000"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0"
integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==
uglify-js@3.4.x, uglify-js@^3.1.4:
version "3.4.9"