-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
index.ts
191 lines (165 loc) · 5.28 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import { join, dirname, basename } from 'path';
import execa from 'execa';
import fs from 'fs';
import { promisify } from 'util';
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
import {
getWriteableDirectory,
download,
glob,
createLambda,
shouldServe,
BuildOptions,
debug,
} from '@now/build-utils';
import { installRequirement, installRequirementsFile } from './install';
async function pipenvConvert(cmd: string, srcDir: string) {
debug('Running pipfile2req...');
try {
const out = await execa.stdout(cmd, [], {
cwd: srcDir,
});
fs.writeFileSync(join(srcDir, 'requirements.txt'), out);
} catch (err) {
console.log('Failed to run "pipfile2req"');
throw err;
}
}
export const version = 3;
export async function downloadFilesInWorkPath({
entrypoint,
workPath,
files,
meta,
config,
}: BuildOptions) {
debug('Downloading user files...');
let downloadedFiles = await download(files, workPath, meta);
if (meta && meta.isDev) {
let base = null;
if (config && config.zeroConfig) {
base = workPath;
} else {
base = dirname(downloadedFiles['now.json'].fsPath);
}
const destNow = join(base, '.now', 'cache', basename(entrypoint, '.py'));
await download(downloadedFiles, destNow);
downloadedFiles = await glob('**', destNow);
workPath = destNow;
}
return workPath;
}
export const build = async ({
workPath,
files: originalFiles,
entrypoint,
meta = {},
config,
}: BuildOptions) => {
workPath = await downloadFilesInWorkPath({
workPath,
files: originalFiles,
entrypoint,
meta,
config,
});
try {
// See: https://stackoverflow.com/a/44728772/376773
//
// The `setup.cfg` is required for `now dev` on MacOS, where without
// this file being present in the src dir then this error happens:
//
// distutils.errors.DistutilsOptionError: must supply either home
// or prefix/exec-prefix -- not both
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;
}
console.log('Installing required dependencies...');
await installRequirement({
dependency: 'werkzeug',
workPath,
meta,
});
let fsFiles = await glob('**', workPath);
const entryDirectory = dirname(entrypoint);
const pipfileLockDir = fsFiles[join(entryDirectory, 'Pipfile.lock')]
? join(workPath, entryDirectory)
: fsFiles['Pipfile.lock']
? workPath
: null;
if (pipfileLockDir) {
debug('Found "Pipfile.lock"');
// Convert Pipenv.Lock to requirements.txt.
// We use a different`workPath` here because we want `pipfile-requirements` and it's dependencies
// to not be part of the lambda environment. By using pip's `--target` directive we can isolate
// it into a separate folder.
const tempDir = await getWriteableDirectory();
await installRequirement({
dependency: 'pipfile-requirements',
workPath: tempDir,
meta,
args: ['--no-warn-script-location'],
});
// Python needs to know where to look up all the packages we just installed.
// We tell it to use the same location as used with `--target`
process.env.PYTHONPATH = tempDir;
const convertCmd = join(tempDir, 'bin', 'pipfile2req');
await pipenvConvert(convertCmd, pipfileLockDir);
}
fsFiles = await glob('**', workPath);
const requirementsTxt = join(entryDirectory, 'requirements.txt');
if (fsFiles[requirementsTxt]) {
debug('Found local "requirements.txt"');
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
await installRequirementsFile({
filePath: requirementsTxtPath,
workPath,
meta,
});
} else if (fsFiles['requirements.txt']) {
debug('Found global "requirements.txt"');
const requirementsTxtPath = fsFiles['requirements.txt'].fsPath;
await installRequirementsFile({
filePath: requirementsTxtPath,
workPath,
meta,
});
}
const originalPyPath = join(__dirname, '..', 'now_init.py');
const originalNowHandlerPyContents = await readFile(originalPyPath, 'utf8');
// will be used on `from $here import handler`
// for example, `from api.users import handler`
debug('Entrypoint is', entrypoint);
const userHandlerFilePath = entrypoint
.replace(/\//g, '.')
.replace(/\.py$/, '');
const nowHandlerPyContents = originalNowHandlerPyContents.replace(
/__NOW_HANDLER_FILENAME/g,
userHandlerFilePath
);
// in order to allow the user to have `server.py`, we need our `server.py` to be called
// somethig else
const nowHandlerPyFilename = 'now__handler__python';
await writeFile(
join(workPath, `${nowHandlerPyFilename}.py`),
nowHandlerPyContents
);
// Use the system-installed version of `python3` when running via `now dev`
const runtime = meta.isDev ? 'python3' : 'python3.6';
const lambda = await createLambda({
files: await glob('**', workPath),
handler: `${nowHandlerPyFilename}.now_handler`,
runtime,
environment: {},
});
return { output: lambda };
};
export { shouldServe };
// internal only - expect breaking changes if other packages depend on these exports
export { installRequirement, installRequirementsFile };