UNPKG

7.06 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const fs_1 = __importDefault(require("fs"));
7const path_1 = require("path");
8const util_1 = require("util");
9const execa_1 = __importDefault(require("execa"));
10const fs_extra_1 = require("fs-extra");
11const micromatch_1 = __importDefault(require("micromatch"));
12const readFile = util_1.promisify(fs_1.default.readFile);
13const writeFile = util_1.promisify(fs_1.default.writeFile);
14const stat = util_1.promisify(fs_1.default.stat);
15const build_utils_1 = require("@now/build-utils");
16const gem_install_1 = require("./gem-install");
17const REQUIRED_VENDOR_DIR = 'vendor/bundle/ruby/2.5.0';
18async function dirExists(directory) {
19 let dirStat;
20 try {
21 dirStat = await stat(directory);
22 }
23 catch (e) {
24 return false;
25 }
26 return dirStat.isDirectory();
27}
28async function gemInstall(gemPath, vendorDir, gems) {
29 console.log(`running "gem install ${gems.join(' ')}"...`);
30 try {
31 await execa_1.default(gemPath, ['install', '--install-dir', vendorDir, '--no-document', ...gems], {
32 stdio: 'inherit',
33 });
34 }
35 catch (err) {
36 console.log(`failed to run "gem install ${gems.join(' ')}"...`);
37 throw err;
38 }
39}
40async function bundleInstall(bundlePath, bundleDir, gemfilePath) {
41 console.log(`running "bundle install --deployment"...`);
42 try {
43 await execa_1.default(bundlePath, [
44 'install',
45 '--deployment',
46 '--gemfile',
47 gemfilePath,
48 '--path',
49 bundleDir,
50 ], {
51 stdio: 'inherit',
52 env: {
53 BUNDLE_SILENCE_ROOT_WARNING: '1',
54 },
55 });
56 }
57 catch (err) {
58 console.log(`failed to run "bundle install --deployment"...`);
59 throw err;
60 }
61}
62exports.config = {
63 maxLambdaSize: '5mb',
64};
65exports.build = async ({ workPath, files, entrypoint, config, }) => {
66 console.log('downloading files...');
67 // eslint-disable-next-line no-param-reassign
68 files = await build_utils_1.download(files, workPath);
69 // this is where `ruby, gem and bundler` will be installed to
70 // we need it to be under `/tmp`
71 const gemHome = await build_utils_1.getWriteableDirectory();
72 process.env.GEM_HOME = gemHome;
73 const { gemPath, bundlerPath } = await gem_install_1.downloadAndInstallBundler();
74 const fsFiles = await build_utils_1.glob('**', workPath);
75 const entryDirectory = path_1.dirname(entrypoint);
76 const fsEntryDirectory = path_1.dirname(fsFiles[entrypoint].fsPath);
77 // check for an existing vendor directory
78 console.log('checking for existing vendor directory at', '"' + REQUIRED_VENDOR_DIR + '"');
79 const vendorDir = path_1.join(workPath, REQUIRED_VENDOR_DIR);
80 const bundleDir = path_1.join(workPath, 'vendor/bundle');
81 const relativeVendorDir = path_1.join(fsEntryDirectory, REQUIRED_VENDOR_DIR);
82 let hasRootVendorDir = await dirExists(vendorDir);
83 let hasRelativeVendorDir = await dirExists(relativeVendorDir);
84 let hasVendorDir = hasRootVendorDir || hasRelativeVendorDir;
85 if (hasRelativeVendorDir) {
86 if (hasRootVendorDir) {
87 console.log('found two vendor directories, choosing the vendor directory relative to entrypoint');
88 }
89 else {
90 console.log('found vendor directory relative to entrypoint');
91 }
92 // vendor dir must be at the root for lambda to find it
93 await fs_extra_1.move(relativeVendorDir, vendorDir);
94 }
95 else if (hasRootVendorDir) {
96 console.log('found vendor directory in project root');
97 }
98 await fs_extra_1.ensureDir(vendorDir);
99 // install single requirement for http
100 await gemInstall(gemPath, vendorDir, ['httparty']);
101 // no vendor directory, check for Gemfile to install
102 if (!hasVendorDir) {
103 const gemFile = path_1.join(entryDirectory, 'Gemfile');
104 if (fsFiles[gemFile]) {
105 console.log('did not find a vendor directory but found a Gemfile, bundling gems...');
106 const gemfilePath = fsFiles[gemFile].fsPath;
107 // try installing. this won't work if native extesions are required.
108 // if that's the case, gems should be vendored locally before deploying.
109 try {
110 await bundleInstall(bundlerPath, bundleDir, gemfilePath);
111 }
112 catch (err) {
113 console.log('unable to build gems from Gemfile. vendor the gems locally with "bundle install --deployment" and retry.');
114 throw err;
115 }
116 }
117 }
118 else {
119 console.log('found vendor directory, skipping "bundle install"...');
120 }
121 // try to remove gem cache to slim bundle size
122 try {
123 await fs_extra_1.remove(path_1.join(vendorDir, 'cache'));
124 }
125 catch (e) { }
126 console.log('calculating size of vendor directory...');
127 await execa_1.default('du', ['-hs', vendorDir], { stdio: 'inherit' });
128 const originalRbPath = path_1.join(__dirname, 'now_init.rb');
129 const originalNowHandlerRbContents = await readFile(originalRbPath, 'utf8');
130 // will be used on `require_relative '$here'`
131 // for example, `require_relative 'api/users'`
132 console.log('entrypoint is', entrypoint);
133 const userHandlerFilePath = entrypoint.replace(/\.rb$/, '');
134 const nowHandlerRbContents = originalNowHandlerRbContents.replace(/__NOW_HANDLER_FILENAME/g, userHandlerFilePath);
135 // in order to allow the user to have `server.rb`, we need our `server.rb` to be called
136 // somethig else
137 const nowHandlerRbFilename = 'now__handler__ruby';
138 await writeFile(path_1.join(workPath, `${nowHandlerRbFilename}.rb`), nowHandlerRbContents);
139 let outputFiles = await build_utils_1.glob('**', workPath);
140 // static analysis is impossible with ruby.
141 // instead, provide `include` and `exclude` config options to reduce bundle size.
142 if (config && (config.include || config.exclude)) {
143 let outputPaths = Object.keys(outputFiles);
144 let notIncluded = config.include
145 ? micromatch_1.default.not(outputPaths, config.include)
146 : outputPaths;
147 let excluded = config.exclude ? micromatch_1.default(notIncluded, config.exclude) : [];
148 for (let i = 0; i < excluded.length; i++) {
149 // whitelist handler
150 if (excluded[i] === `${nowHandlerRbFilename}.rb`) {
151 continue;
152 }
153 // whitelist vendor directory
154 if (excluded[i].startsWith(REQUIRED_VENDOR_DIR)) {
155 continue;
156 }
157 delete outputFiles[excluded[i]];
158 }
159 }
160 const lambda = await build_utils_1.createLambda({
161 files: outputFiles,
162 handler: `${nowHandlerRbFilename}.now__handler`,
163 runtime: 'ruby2.5',
164 environment: {},
165 });
166 return {
167 [entrypoint]: lambda,
168 };
169};