mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
40 Commits
vercel-plu
...
vercel-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6792edf32a | ||
|
|
67de167a7e | ||
|
|
0c5c05d90b | ||
|
|
fe43c9c4b2 | ||
|
|
d6a5aa4f6d | ||
|
|
1c3701628d | ||
|
|
45689f22ab | ||
|
|
2c3ddffaac | ||
|
|
c3ea0195c2 | ||
|
|
5f5e50cff0 | ||
|
|
160f4d46d9 | ||
|
|
8d619bd7cc | ||
|
|
b94337d842 | ||
|
|
34f4222ca2 | ||
|
|
5de045edd7 | ||
|
|
5efd3b98de | ||
|
|
82c83312c7 | ||
|
|
5ccb983007 | ||
|
|
7a921399be | ||
|
|
3900f2f982 | ||
|
|
09939f1e07 | ||
|
|
fc3a3ca81f | ||
|
|
ba7bf2e4a6 | ||
|
|
00641037fc | ||
|
|
6f4a1b527b | ||
|
|
1b95576dd2 | ||
|
|
9227471aca | ||
|
|
bf060296eb | ||
|
|
9b3aa41f2e | ||
|
|
ae36585cdb | ||
|
|
e4c636ddd2 | ||
|
|
ae3b25be4b | ||
|
|
a64ed13a40 | ||
|
|
6c1c0e6676 | ||
|
|
82fdd5d121 | ||
|
|
8b40f4435e | ||
|
|
38c87602bb | ||
|
|
7aef3013e7 | ||
|
|
c18676ab4d | ||
|
|
df450c815d |
40
.github/CODEOWNERS
vendored
40
.github/CODEOWNERS
vendored
@@ -4,24 +4,26 @@
|
||||
* @TooTallNate
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @styfle @lucleray
|
||||
/packages/client @rdev @styfle @TooTallNate
|
||||
/packages/build-utils @styfle @AndyBitz @TooTallNate
|
||||
/packages/node @styfle @TooTallNate @lucleray
|
||||
/packages/node-bridge @styfle @TooTallNate @lucleray
|
||||
/packages/next @Timer @ijjk
|
||||
/packages/go @styfle @TooTallNate
|
||||
/packages/python @styfle @TooTallNate
|
||||
/packages/ruby @styfle @coetry @TooTallNate
|
||||
/packages/static-build @styfle @AndyBitz
|
||||
/packages/routing-utils @styfle @dav-is @ijjk
|
||||
/examples @mcsdevv @timothyis
|
||||
/packages/cli/src/commands/build @TooTallNate @styfle @AndyBitz @gdborton @jaredpalmer
|
||||
/packages/cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/cli/src/commands/env @styfle @lucleray
|
||||
/packages/client @styfle @TooTallNate
|
||||
/packages/build-utils @styfle @AndyBitz @TooTallNate
|
||||
/packages/middleware @gdborton @javivelasco
|
||||
/packages/node @styfle @TooTallNate @lucleray
|
||||
/packages/node-bridge @styfle @TooTallNate @lucleray
|
||||
/packages/next @Timer @ijjk
|
||||
/packages/go @styfle @TooTallNate
|
||||
/packages/python @styfle @TooTallNate
|
||||
/packages/ruby @styfle @TooTallNate
|
||||
/packages/static-build @styfle @AndyBitz
|
||||
/packages/routing-utils @styfle @dav-is @ijjk
|
||||
/examples @mcsdevv
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens @Timer
|
||||
/examples/hugo @mcsdevv @timothyis @styfle
|
||||
/examples/jekyll @mcsdevv @timothyis @styfle
|
||||
/examples/zola @mcsdevv @timothyis @styfle
|
||||
/examples/hugo @mcsdevv @styfle
|
||||
/examples/jekyll @mcsdevv @styfle
|
||||
/examples/zola @mcsdevv @styfle
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "API for the vercel/vercel repo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"vercel-build": "yarn --cwd .. && node ../utils/run.js build all"
|
||||
"vercel-build": "node ../utils/run.js build all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.12.3-canary.28",
|
||||
"version": "2.12.3-canary.38",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "^2.4.1",
|
||||
"@vercel/frameworks": "0.5.1-canary.16",
|
||||
"@vercel/frameworks": "0.5.1-canary.17",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
import fs from 'fs-extra';
|
||||
import { join, dirname, relative } from 'path';
|
||||
import { join, parse, relative, dirname, basename, extname } from 'path';
|
||||
import glob from './fs/glob';
|
||||
import { normalizePath } from './fs/normalize-path';
|
||||
import { FILES_SYMBOL, Lambda } from './lambda';
|
||||
import type FileBlob from './file-blob';
|
||||
import type { BuildOptions, Files } from './types';
|
||||
import { getIgnoreFilter } from '.';
|
||||
import { debug, getIgnoreFilter } from '.';
|
||||
|
||||
// `.output` was already created by the Build Command, so we have
|
||||
// to ensure its contents don't get bundled into the Lambda. Similarily,
|
||||
// we don't want to bundle anything from `.vercel` either. Lastly,
|
||||
// Builders/Runtimes didn't have `vercel.json` or `now.json`.
|
||||
const ignoredPaths = ['.output', '.vercel', 'vercel.json', 'now.json'];
|
||||
|
||||
const shouldIgnorePath = (
|
||||
file: string,
|
||||
ignoreFilter: any,
|
||||
ignoreFile: boolean
|
||||
) => {
|
||||
const isNative = ignoredPaths.some(item => {
|
||||
return file.startsWith(item);
|
||||
});
|
||||
|
||||
if (!ignoreFile) {
|
||||
return isNative;
|
||||
}
|
||||
|
||||
return isNative || ignoreFilter(file);
|
||||
};
|
||||
|
||||
const getSourceFiles = async (workPath: string, ignoreFilter: any) => {
|
||||
const list = await glob('**', {
|
||||
cwd: workPath,
|
||||
});
|
||||
|
||||
// We're not passing this as an `ignore` filter to the `glob` function above,
|
||||
// so that we can re-use exactly the same `getIgnoreFilter` method that the
|
||||
// Build Step uses (literally the same code). Note that this exclusion only applies
|
||||
// when deploying. Locally, another exclusion is needed, which is handled
|
||||
// further below in the `convertRuntimeToPlugin` function.
|
||||
for (const file in list) {
|
||||
if (shouldIgnorePath(file, ignoreFilter, true)) {
|
||||
delete list[file];
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert legacy Runtime to a Plugin.
|
||||
@@ -20,34 +61,30 @@ export function convertRuntimeToPlugin(
|
||||
) {
|
||||
// This `build()` signature should match `plugin.build()` signature in `vercel build`.
|
||||
return async function build({ workPath }: { workPath: string }) {
|
||||
const opts = { cwd: workPath };
|
||||
const files = await glob('**', opts);
|
||||
|
||||
// `.output` was already created by the Build Command, so we have
|
||||
// to ensure its contents don't get bundled into the Lambda. Similarily,
|
||||
// we don't want to bundle anything from `.vercel` either. Lastly,
|
||||
// Builders/Runtimes didn't have `vercel.json` or `now.json`.
|
||||
const ignoredPaths = ['.output', '.vercel', 'vercel.json', 'now.json'];
|
||||
|
||||
// We also don't want to provide any files to Runtimes that were ignored
|
||||
// through `.vercelignore` or `.nowignore`, because the Build Step does the same.
|
||||
const ignoreFilter = await getIgnoreFilter(workPath);
|
||||
|
||||
// We're not passing this as an `ignore` filter to the `glob` function above,
|
||||
// so that we can re-use exactly the same `getIgnoreFilter` method that the
|
||||
// Build Step uses (literally the same code).
|
||||
for (const file in files) {
|
||||
const isNative = ignoredPaths.some(item => {
|
||||
return file.startsWith(item);
|
||||
});
|
||||
// Retrieve the files that are currently available on the File System,
|
||||
// before the Legacy Runtime has even started to build.
|
||||
const sourceFilesPreBuild = await getSourceFiles(workPath, ignoreFilter);
|
||||
|
||||
if (isNative || ignoreFilter(file)) {
|
||||
delete files[file];
|
||||
// Instead of doing another `glob` to get all the matching source files,
|
||||
// we'll filter the list of existing files down to only the ones
|
||||
// that are matching the entrypoint pattern, so we're first creating
|
||||
// a clean new list to begin.
|
||||
const entrypoints = Object.assign({}, sourceFilesPreBuild);
|
||||
|
||||
const entrypointMatch = new RegExp(`^api/.*${ext}$`);
|
||||
|
||||
// Up next, we'll strip out the files from the list of entrypoints
|
||||
// that aren't actually considered entrypoints.
|
||||
for (const file in entrypoints) {
|
||||
if (!entrypointMatch.test(file)) {
|
||||
delete entrypoints[file];
|
||||
}
|
||||
}
|
||||
|
||||
const entrypointPattern = `api/**/*${ext}`;
|
||||
const entrypoints = await glob(entrypointPattern, opts);
|
||||
const pages: { [key: string]: any } = {};
|
||||
const pluginName = packageName.replace('vercel-plugin-', '');
|
||||
|
||||
@@ -64,9 +101,15 @@ export function convertRuntimeToPlugin(
|
||||
|
||||
await fs.ensureDir(traceDir);
|
||||
|
||||
let newPathsRuntime: Set<string> = new Set();
|
||||
let linkersRuntime: Array<Promise<void>> = [];
|
||||
|
||||
const entryDir = join('.output', 'server', 'pages');
|
||||
const entryRoot = join(workPath, entryDir);
|
||||
|
||||
for (const entrypoint of Object.keys(entrypoints)) {
|
||||
const { output } = await buildRuntime({
|
||||
files,
|
||||
files: sourceFilesPreBuild,
|
||||
entrypoint,
|
||||
workPath,
|
||||
config: {
|
||||
@@ -74,62 +117,238 @@ export function convertRuntimeToPlugin(
|
||||
},
|
||||
meta: {
|
||||
avoidTopLevelInstall: true,
|
||||
skipDownload: true,
|
||||
},
|
||||
});
|
||||
|
||||
pages[entrypoint] = {
|
||||
handler: output.handler,
|
||||
runtime: output.runtime,
|
||||
memory: output.memory,
|
||||
maxDuration: output.maxDuration,
|
||||
environment: output.environment,
|
||||
allowQuery: output.allowQuery,
|
||||
};
|
||||
// Legacy Runtimes tend to pollute the `workPath` with compiled results,
|
||||
// because the `workPath` used to be a place that was a place where they could
|
||||
// just put anything, but nowadays it's the working directory of the `vercel build`
|
||||
// command, which is the place where the developer keeps their source files,
|
||||
// so we don't want to pollute this space unnecessarily. That means we have to clean
|
||||
// up files that were created by the build, which is done further below.
|
||||
const sourceFilesAfterBuild = await getSourceFiles(
|
||||
workPath,
|
||||
ignoreFilter
|
||||
);
|
||||
|
||||
// @ts-ignore This symbol is a private API
|
||||
const lambdaFiles: Files = output[FILES_SYMBOL];
|
||||
|
||||
const entry = join(workPath, '.output', 'server', 'pages', entrypoint);
|
||||
// When deploying, the `files` that are passed to the Legacy Runtimes already
|
||||
// have certain files that are ignored stripped, but locally, that list of
|
||||
// files isn't used by the Legacy Runtimes, so we need to apply the filters
|
||||
// to the outputs that they are returning instead.
|
||||
for (const file in lambdaFiles) {
|
||||
if (shouldIgnorePath(file, ignoreFilter, false)) {
|
||||
delete lambdaFiles[file];
|
||||
}
|
||||
}
|
||||
|
||||
let handlerFileBase = output.handler;
|
||||
let handlerFile = lambdaFiles[handlerFileBase];
|
||||
|
||||
const { handler } = output;
|
||||
const handlerMethod = handler.split('.').pop();
|
||||
const handlerFileName = handler.replace(`.${handlerMethod}`, '');
|
||||
|
||||
// For compiled languages, the launcher file for the Lambda generated
|
||||
// by the Legacy Runtime matches the `handler` defined for it, but for
|
||||
// interpreted languages, the `handler` consists of the launcher file name
|
||||
// without an extension, plus the name of the method inside of that file
|
||||
// that should be invoked, so we have to construct the file path explicitly.
|
||||
if (!handlerFile) {
|
||||
handlerFileBase = handlerFileName + ext;
|
||||
handlerFile = lambdaFiles[handlerFileBase];
|
||||
}
|
||||
|
||||
if (!handlerFile || !handlerFile.fsPath) {
|
||||
throw new Error(
|
||||
`Could not find a handler file. Please ensure that \`files\` for the returned \`Lambda\` contains an \`FileFsRef\` named "${handlerFileBase}" with a valid \`fsPath\`.`
|
||||
);
|
||||
}
|
||||
|
||||
const handlerExtName = extname(handlerFile.fsPath);
|
||||
|
||||
const entryBase = basename(entrypoint).replace(ext, handlerExtName);
|
||||
const entryPath = join(dirname(entrypoint), entryBase);
|
||||
const entry = join(entryRoot, entryPath);
|
||||
|
||||
// We never want to link here, only copy, because the launcher
|
||||
// file often has the same name for every entrypoint, which means that
|
||||
// every build for every entrypoint overwrites the launcher of the previous
|
||||
// one, so linking would end with a broken reference.
|
||||
await fs.ensureDir(dirname(entry));
|
||||
await linkOrCopy(files[entrypoint].fsPath, entry);
|
||||
await fs.copy(handlerFile.fsPath, entry);
|
||||
|
||||
const newFilesEntrypoint: Array<string> = [];
|
||||
const newDirectoriesEntrypoint: Array<string> = [];
|
||||
|
||||
const preBuildFiles = Object.values(sourceFilesPreBuild).map(file => {
|
||||
return file.fsPath;
|
||||
});
|
||||
|
||||
// Generate a list of directories and files that weren't present
|
||||
// before the entrypoint was processed by the Legacy Runtime, so
|
||||
// that we can perform a cleanup later. We need to divide into files
|
||||
// and directories because only cleaning up files might leave empty
|
||||
// directories, and listing directories separately also speeds up the
|
||||
// build because we can just delete them, which wipes all of their nested
|
||||
// paths, instead of iterating through all files that should be deleted.
|
||||
for (const file in sourceFilesAfterBuild) {
|
||||
if (!sourceFilesPreBuild[file]) {
|
||||
const path = sourceFilesAfterBuild[file].fsPath;
|
||||
const dirPath = dirname(path);
|
||||
|
||||
// If none of the files that were present before the entrypoint
|
||||
// was processed are contained within the directory we're looking
|
||||
// at right now, then we know it's a newly added directory
|
||||
// and it can therefore be removed later on.
|
||||
const isNewDir = !preBuildFiles.some(filePath => {
|
||||
return dirname(filePath).startsWith(dirPath);
|
||||
});
|
||||
|
||||
// Check out the list of tracked directories that were
|
||||
// newly added and see if one of them contains the path
|
||||
// we're looking at.
|
||||
const hasParentDir = newDirectoriesEntrypoint.some(dir => {
|
||||
return path.startsWith(dir);
|
||||
});
|
||||
|
||||
// If we have already tracked a directory that was newly
|
||||
// added that sits above the file or directory that we're
|
||||
// looking at, we don't need to add more entries to the list
|
||||
// because when the parent will get removed in the future,
|
||||
// all of its children (and therefore the path we're looking at)
|
||||
// will automatically get removed anyways.
|
||||
if (hasParentDir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNewDir) {
|
||||
newDirectoriesEntrypoint.push(dirPath);
|
||||
} else {
|
||||
newFilesEntrypoint.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tracedFiles: {
|
||||
absolutePath: string;
|
||||
relativePath: string;
|
||||
}[] = [];
|
||||
|
||||
Object.entries(lambdaFiles).forEach(async ([relPath, file]) => {
|
||||
const newPath = join(traceDir, relPath);
|
||||
tracedFiles.push({ absolutePath: newPath, relativePath: relPath });
|
||||
if (file.fsPath) {
|
||||
await linkOrCopy(file.fsPath, newPath);
|
||||
} else if (file.type === 'FileBlob') {
|
||||
const { data, mode } = file as FileBlob;
|
||||
await fs.writeFile(newPath, data, { mode });
|
||||
} else {
|
||||
throw new Error(`Unknown file type: ${file.type}`);
|
||||
}
|
||||
});
|
||||
const linkers = Object.entries(lambdaFiles).map(
|
||||
async ([relPath, file]) => {
|
||||
const newPath = join(traceDir, relPath);
|
||||
|
||||
const nft = join(
|
||||
workPath,
|
||||
'.output',
|
||||
'server',
|
||||
'pages',
|
||||
`${entrypoint}.nft.json`
|
||||
// The handler was already moved into position above.
|
||||
if (relPath === handlerFileBase) {
|
||||
return;
|
||||
}
|
||||
|
||||
tracedFiles.push({ absolutePath: newPath, relativePath: relPath });
|
||||
const { fsPath, type } = file;
|
||||
|
||||
if (fsPath) {
|
||||
await fs.ensureDir(dirname(newPath));
|
||||
|
||||
const isNewFile = newFilesEntrypoint.includes(fsPath);
|
||||
|
||||
const isInsideNewDirectory = newDirectoriesEntrypoint.some(
|
||||
dirPath => {
|
||||
return fsPath.startsWith(dirPath);
|
||||
}
|
||||
);
|
||||
|
||||
// With this, we're making sure that files in the `workPath` that existed
|
||||
// before the Legacy Runtime was invoked (source files) are linked from
|
||||
// `.output` instead of copying there (the latter only happens if linking fails),
|
||||
// which is the fastest solution. However, files that are created fresh
|
||||
// by the Legacy Runtimes are always copied, because their link destinations
|
||||
// are likely to be overwritten every time an entrypoint is processed by
|
||||
// the Legacy Runtime. This is likely to overwrite the destination on subsequent
|
||||
// runs, but that's also how `workPath` used to work originally, without
|
||||
// the File System API (meaning that there was one `workPath` for all entrypoints).
|
||||
if (isNewFile || isInsideNewDirectory) {
|
||||
debug(`Copying from ${fsPath} to ${newPath}`);
|
||||
await fs.copy(fsPath, newPath);
|
||||
} else {
|
||||
await linkOrCopy(fsPath, newPath);
|
||||
}
|
||||
} else if (type === 'FileBlob') {
|
||||
const { data, mode } = file as FileBlob;
|
||||
await fs.writeFile(newPath, data, { mode });
|
||||
} else {
|
||||
throw new Error(`Unknown file type: ${type}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
linkersRuntime = linkersRuntime.concat(linkers);
|
||||
|
||||
const nft = `${entry}.nft.json`;
|
||||
|
||||
const json = JSON.stringify({
|
||||
version: 1,
|
||||
files: tracedFiles.map(f => ({
|
||||
input: normalizePath(relative(nft, f.absolutePath)),
|
||||
output: normalizePath(f.relativePath),
|
||||
files: tracedFiles.map(file => ({
|
||||
input: normalizePath(relative(dirname(nft), file.absolutePath)),
|
||||
// We'd like to place all the dependency files right next
|
||||
// to the final launcher file inside of the Lambda.
|
||||
output: normalizePath(join(entryDir, 'api', file.relativePath)),
|
||||
})),
|
||||
});
|
||||
|
||||
await fs.ensureDir(dirname(nft));
|
||||
await fs.writeFile(nft, json);
|
||||
|
||||
// Extend the list of directories and files that were created by the
|
||||
// Legacy Runtime with the list of directories and files that were
|
||||
// created for the entrypoint that was just processed above.
|
||||
newPathsRuntime = new Set([
|
||||
...newPathsRuntime,
|
||||
...newFilesEntrypoint,
|
||||
...newDirectoriesEntrypoint,
|
||||
]);
|
||||
|
||||
// Add an entry that will later on be added to the `functions-manifest.json`
|
||||
// file that is placed inside of the `.output` directory.
|
||||
pages[normalizePath(entryPath)] = {
|
||||
// Because the underlying file used as a handler was placed
|
||||
// inside `.output/server/pages/api`, it no longer has the name it originally
|
||||
// had and is now named after the API Route that it's responsible for,
|
||||
// so we have to adjust the name of the Lambda handler accordingly.
|
||||
handler: handler.replace(handlerFileName, parse(entry).name),
|
||||
runtime: output.runtime,
|
||||
memory: output.memory,
|
||||
maxDuration: output.maxDuration,
|
||||
environment: output.environment,
|
||||
allowQuery: output.allowQuery,
|
||||
};
|
||||
}
|
||||
|
||||
// Instead of of waiting for all of the linking to be done for every
|
||||
// entrypoint before processing the next one, we immediately handle all
|
||||
// of them one after the other, while then waiting for the linking
|
||||
// to finish right here, before we clean up newly created files below.
|
||||
await Promise.all(linkersRuntime);
|
||||
|
||||
// A list of all the files that were created by the Legacy Runtime,
|
||||
// which we'd like to remove from the File System.
|
||||
const toRemove = Array.from(newPathsRuntime).map(path => {
|
||||
debug(`Removing ${path} as part of cleanup`);
|
||||
return fs.remove(path);
|
||||
});
|
||||
|
||||
// Once all the entrypoints have been processed, we'd like to
|
||||
// remove all the files from `workPath` that originally weren't present
|
||||
// before the Legacy Runtime began running, because the `workPath`
|
||||
// is nowadays the directory in which the user keeps their source code, since
|
||||
// we're no longer running separate parallel builds for every Legacy Runtime.
|
||||
await Promise.all(toRemove);
|
||||
|
||||
// Add any Serverless Functions that were exposed by the Legacy Runtime
|
||||
// to the `functions-manifest.json` file provided in `.output`.
|
||||
await updateFunctionsManifest({ workPath, pages });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { BuildOptions, createLambda } from '../src';
|
||||
import { BuildOptions, createLambda, FileFsRef } from '../src';
|
||||
import { convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
|
||||
|
||||
async function fsToJson(dir: string, output: Record<string, any> = {}) {
|
||||
@@ -32,9 +32,13 @@ describe('convert-runtime-to-plugin', () => {
|
||||
});
|
||||
|
||||
it('should create correct fileystem for python', async () => {
|
||||
const ext = '.py';
|
||||
const workPath = pythonApiWorkpath;
|
||||
const handlerName = 'vc__handler__python';
|
||||
const handlerFileName = handlerName + ext;
|
||||
|
||||
const lambdaOptions = {
|
||||
handler: 'index.handler',
|
||||
handler: `${handlerName}.vc_handler`,
|
||||
runtime: 'python3.9',
|
||||
memory: 512,
|
||||
maxDuration: 5,
|
||||
@@ -42,6 +46,15 @@ describe('convert-runtime-to-plugin', () => {
|
||||
};
|
||||
|
||||
const buildRuntime = async (opts: BuildOptions) => {
|
||||
const handlerPath = join(workPath, handlerFileName);
|
||||
|
||||
// This is the usual time at which a Legacy Runtime writes its Lambda launcher.
|
||||
await fs.writeFile(handlerPath, '# handler');
|
||||
|
||||
opts.files[handlerFileName] = new FileFsRef({
|
||||
fsPath: handlerPath,
|
||||
});
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: opts.files,
|
||||
...lambdaOptions,
|
||||
@@ -50,9 +63,6 @@ describe('convert-runtime-to-plugin', () => {
|
||||
};
|
||||
|
||||
const lambdaFiles = await fsToJson(workPath);
|
||||
delete lambdaFiles['vercel.json'];
|
||||
|
||||
const ext = '.py';
|
||||
const packageName = 'vercel-plugin-python';
|
||||
const build = await convertRuntimeToPlugin(buildRuntime, packageName, ext);
|
||||
|
||||
@@ -60,6 +70,9 @@ describe('convert-runtime-to-plugin', () => {
|
||||
|
||||
const output = await fsToJson(join(workPath, '.output'));
|
||||
|
||||
delete lambdaFiles['vercel.json'];
|
||||
delete lambdaFiles['vc__handler__python.py'];
|
||||
|
||||
expect(output).toMatchObject({
|
||||
'functions-manifest.json': expect.stringContaining('{'),
|
||||
inputs: {
|
||||
@@ -68,12 +81,12 @@ describe('convert-runtime-to-plugin', () => {
|
||||
server: {
|
||||
pages: {
|
||||
api: {
|
||||
'index.py': expect.stringContaining('index'),
|
||||
'index.py': expect.stringContaining('handler'),
|
||||
'index.py.nft.json': expect.stringContaining('{'),
|
||||
users: {
|
||||
'get.py': expect.stringContaining('get'),
|
||||
'get.py': expect.stringContaining('handler'),
|
||||
'get.py.nft.json': expect.stringContaining('{'),
|
||||
'post.py': expect.stringContaining('post'),
|
||||
'post.py': expect.stringContaining('handler'),
|
||||
'post.py.nft.json': expect.stringContaining('{'),
|
||||
},
|
||||
},
|
||||
@@ -85,9 +98,13 @@ describe('convert-runtime-to-plugin', () => {
|
||||
expect(funcManifest).toMatchObject({
|
||||
version: 1,
|
||||
pages: {
|
||||
'api/index.py': lambdaOptions,
|
||||
'api/users/get.py': lambdaOptions,
|
||||
'api/users/post.py': { ...lambdaOptions, memory: 512 },
|
||||
'api/index.py': { ...lambdaOptions, handler: 'index.vc_handler' },
|
||||
'api/users/get.py': { ...lambdaOptions, handler: 'get.vc_handler' },
|
||||
'api/users/post.py': {
|
||||
...lambdaOptions,
|
||||
handler: 'post.vc_handler',
|
||||
memory: 512,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -96,36 +113,36 @@ describe('convert-runtime-to-plugin', () => {
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: 'api/db/[id].py',
|
||||
input: `../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: '.output/server/pages/api/api/db/[id].py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/api/index.py`,
|
||||
output: 'api/index.py',
|
||||
input: `../../../inputs/api-routes-python/api/index.py`,
|
||||
output: '.output/server/pages/api/api/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: 'api/project/[aid]/[bid]/index.py',
|
||||
input: `../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: '.output/server/pages/api/api/project/[aid]/[bid]/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: 'api/users/get.py',
|
||||
input: `../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: '.output/server/pages/api/api/users/get.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: 'api/users/post.py',
|
||||
input: `../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: '.output/server/pages/api/api/users/post.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/file.txt`,
|
||||
output: 'file.txt',
|
||||
input: `../../../inputs/api-routes-python/file.txt`,
|
||||
output: '.output/server/pages/api/file.txt',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/util/date.py`,
|
||||
output: 'util/date.py',
|
||||
input: `../../../inputs/api-routes-python/util/date.py`,
|
||||
output: '.output/server/pages/api/util/date.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../inputs/api-routes-python/util/math.py`,
|
||||
output: 'util/math.py',
|
||||
input: `../../../inputs/api-routes-python/util/math.py`,
|
||||
output: '.output/server/pages/api/util/math.py',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -137,36 +154,36 @@ describe('convert-runtime-to-plugin', () => {
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: 'api/db/[id].py',
|
||||
input: `../../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: '.output/server/pages/api/api/db/[id].py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/index.py`,
|
||||
output: 'api/index.py',
|
||||
input: `../../../../inputs/api-routes-python/api/index.py`,
|
||||
output: '.output/server/pages/api/api/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: 'api/project/[aid]/[bid]/index.py',
|
||||
input: `../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: '.output/server/pages/api/api/project/[aid]/[bid]/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: 'api/users/get.py',
|
||||
input: `../../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: '.output/server/pages/api/api/users/get.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: 'api/users/post.py',
|
||||
input: `../../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: '.output/server/pages/api/api/users/post.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/file.txt`,
|
||||
output: 'file.txt',
|
||||
input: `../../../../inputs/api-routes-python/file.txt`,
|
||||
output: '.output/server/pages/api/file.txt',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/util/date.py`,
|
||||
output: 'util/date.py',
|
||||
input: `../../../../inputs/api-routes-python/util/date.py`,
|
||||
output: '.output/server/pages/api/util/date.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/util/math.py`,
|
||||
output: 'util/math.py',
|
||||
input: `../../../../inputs/api-routes-python/util/math.py`,
|
||||
output: '.output/server/pages/api/util/math.py',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -178,36 +195,36 @@ describe('convert-runtime-to-plugin', () => {
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: 'api/db/[id].py',
|
||||
input: `../../../../inputs/api-routes-python/api/db/[id].py`,
|
||||
output: '.output/server/pages/api/api/db/[id].py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/index.py`,
|
||||
output: 'api/index.py',
|
||||
input: `../../../../inputs/api-routes-python/api/index.py`,
|
||||
output: '.output/server/pages/api/api/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: 'api/project/[aid]/[bid]/index.py',
|
||||
input: `../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
|
||||
output: '.output/server/pages/api/api/project/[aid]/[bid]/index.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: 'api/users/get.py',
|
||||
input: `../../../../inputs/api-routes-python/api/users/get.py`,
|
||||
output: '.output/server/pages/api/api/users/get.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: 'api/users/post.py',
|
||||
input: `../../../../inputs/api-routes-python/api/users/post.py`,
|
||||
output: '.output/server/pages/api/api/users/post.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/file.txt`,
|
||||
output: 'file.txt',
|
||||
input: `../../../../inputs/api-routes-python/file.txt`,
|
||||
output: '.output/server/pages/api/file.txt',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/util/date.py`,
|
||||
output: 'util/date.py',
|
||||
input: `../../../../inputs/api-routes-python/util/date.py`,
|
||||
output: '.output/server/pages/api/util/date.py',
|
||||
},
|
||||
{
|
||||
input: `../../../../../inputs/api-routes-python/util/math.py`,
|
||||
output: 'util/math.py',
|
||||
input: `../../../../inputs/api-routes-python/util/math.py`,
|
||||
output: '.output/server/pages/api/util/math.py',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "23.1.3-canary.47",
|
||||
"version": "23.1.3-canary.61",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -43,14 +43,14 @@
|
||||
"node": ">= 12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/go": "1.2.4-canary.4",
|
||||
"@vercel/node": "1.12.2-canary.7",
|
||||
"@vercel/python": "2.1.2-canary.1",
|
||||
"@vercel/ruby": "1.2.8-canary.6",
|
||||
"@vercel/ruby": "1.2.10-canary.0",
|
||||
"update-notifier": "4.1.0",
|
||||
"vercel-plugin-middleware": "0.0.0-canary.7",
|
||||
"vercel-plugin-node": "1.12.2-canary.19"
|
||||
"vercel-plugin-middleware": "0.0.0-canary.14",
|
||||
"vercel-plugin-node": "1.12.2-canary.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/env": "11.1.2",
|
||||
@@ -90,7 +90,7 @@
|
||||
"@types/update-notifier": "5.1.0",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@vercel/frameworks": "0.5.1-canary.16",
|
||||
"@vercel/frameworks": "0.5.1-canary.17",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.17.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GlobOptions,
|
||||
scanParentDirs,
|
||||
spawnAsync,
|
||||
glob as buildUtilsGlob,
|
||||
} from '@vercel/build-utils';
|
||||
import { nodeFileTrace } from '@vercel/nft';
|
||||
import Sema from 'async-sema';
|
||||
@@ -353,13 +354,19 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
// We cannot rely on the `framework` alone, as it might be a static export,
|
||||
// and the current build might use a differnt project that's not in the settings.
|
||||
// and the current build might use a different project that's not in the settings.
|
||||
const isNextOutput = Boolean(dotNextDir);
|
||||
const outputDir = isNextOutput ? OUTPUT_DIR : join(OUTPUT_DIR, 'static');
|
||||
const nextExport = await getNextExportStatus(dotNextDir);
|
||||
const outputDir =
|
||||
isNextOutput && !nextExport ? OUTPUT_DIR : join(OUTPUT_DIR, 'static');
|
||||
const getDistDir = framework.getFsOutputDir || framework.getOutputDirName;
|
||||
const distDir =
|
||||
(nextExport?.exportDetail.outDirectory
|
||||
? relative(cwd, nextExport.exportDetail.outDirectory)
|
||||
: false) ||
|
||||
dotNextDir ||
|
||||
userOutputDirectory ||
|
||||
(await framework.getFsOutputDir(cwd));
|
||||
(await getDistDir(cwd));
|
||||
|
||||
await fs.ensureDir(join(cwd, outputDir));
|
||||
|
||||
@@ -443,7 +450,53 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
// Special Next.js processing.
|
||||
if (isNextOutput) {
|
||||
if (nextExport) {
|
||||
client.output.debug('Found `next export` output.');
|
||||
|
||||
const htmlFiles = await buildUtilsGlob(
|
||||
'**/*.html',
|
||||
join(cwd, OUTPUT_DIR, 'static')
|
||||
);
|
||||
|
||||
if (nextExport.exportDetail.success !== true) {
|
||||
client.output.error(
|
||||
`Export of Next.js app failed. Please check your build logs.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'server', 'pages'));
|
||||
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static'));
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(htmlFiles).map(async fileName => {
|
||||
await sema.acquire();
|
||||
|
||||
const input = join(cwd, OUTPUT_DIR, 'static', fileName);
|
||||
const target = join(cwd, OUTPUT_DIR, 'server', 'pages', fileName);
|
||||
|
||||
await fs.mkdirp(dirname(target));
|
||||
|
||||
await fs.promises.rename(input, target).finally(() => {
|
||||
sema.release();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
for (const file of [
|
||||
'BUILD_ID',
|
||||
'images-manifest.json',
|
||||
'routes-manifest.json',
|
||||
'build-manifest.json',
|
||||
]) {
|
||||
const input = join(nextExport.dotNextDir, file);
|
||||
|
||||
if (fs.existsSync(input)) {
|
||||
// Do not use `smartCopy`, since we want to overwrite if they already exist.
|
||||
await fs.copyFile(input, join(OUTPUT_DIR, file));
|
||||
}
|
||||
}
|
||||
} else if (isNextOutput) {
|
||||
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
|
||||
const tempStatic = '___static';
|
||||
await fs.rename(
|
||||
@@ -612,32 +665,37 @@ export default async function main(client: Client) {
|
||||
}
|
||||
}
|
||||
|
||||
client.output.debug(`Resolve ${param('required-server-files.json')}.`);
|
||||
const requiredServerFilesPath = join(
|
||||
OUTPUT_DIR,
|
||||
'required-server-files.json'
|
||||
);
|
||||
const requiredServerFilesJson = await fs.readJSON(
|
||||
requiredServerFilesPath
|
||||
);
|
||||
await fs.writeJSON(requiredServerFilesPath, {
|
||||
...requiredServerFilesJson,
|
||||
appDir: '.',
|
||||
files: requiredServerFilesJson.files.map((i: string) => {
|
||||
const originalPath = join(requiredServerFilesJson.appDir, i);
|
||||
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
|
||||
|
||||
const absolutePath = join(cwd, relPath);
|
||||
const output = relative(baseDir, absolutePath);
|
||||
if (fs.existsSync(requiredServerFilesPath)) {
|
||||
client.output.debug(`Resolve ${param('required-server-files.json')}.`);
|
||||
|
||||
return relPath === output
|
||||
? relPath
|
||||
: {
|
||||
input: relPath,
|
||||
output,
|
||||
};
|
||||
}),
|
||||
});
|
||||
const requiredServerFilesJson = await fs.readJSON(
|
||||
requiredServerFilesPath
|
||||
);
|
||||
|
||||
await fs.writeJSON(requiredServerFilesPath, {
|
||||
...requiredServerFilesJson,
|
||||
appDir: '.',
|
||||
files: requiredServerFilesJson.files.map((i: string) => {
|
||||
const originalPath = join(requiredServerFilesJson.appDir, i);
|
||||
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
|
||||
|
||||
const absolutePath = join(cwd, relPath);
|
||||
const output = relative(baseDir, absolutePath);
|
||||
|
||||
return relPath === output
|
||||
? relPath
|
||||
: {
|
||||
input: relPath,
|
||||
output,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,3 +930,53 @@ async function resolveNftToOutput({
|
||||
files: newFilesList,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Files will only exist when `next export` was used.
|
||||
*/
|
||||
async function getNextExportStatus(dotNextDir: string | null) {
|
||||
if (!dotNextDir) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const exportDetail: {
|
||||
success: boolean;
|
||||
outDirectory: string;
|
||||
} | null = await fs
|
||||
.readJson(join(dotNextDir, 'export-detail.json'))
|
||||
.catch(error => {
|
||||
if (error.code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
if (!exportDetail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const exportMarker: {
|
||||
version: 1;
|
||||
exportTrailingSlash: boolean;
|
||||
hasExportPathMap: boolean;
|
||||
} | null = await fs
|
||||
.readJSON(join(dotNextDir, 'export-marker.json'))
|
||||
.catch(error => {
|
||||
if (error.code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
return {
|
||||
dotNextDir,
|
||||
exportDetail,
|
||||
exportMarker: {
|
||||
trailingSlash: exportMarker?.hasExportPathMap
|
||||
? exportMarker.exportTrailingSlash
|
||||
: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -968,7 +968,7 @@ export default class DevServer {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
const target = `http://localhost:${this.devProcessPort}`;
|
||||
const target = `http://127.0.0.1:${this.devProcessPort}`;
|
||||
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
|
||||
this.proxy.ws(req, socket, head, { target });
|
||||
});
|
||||
@@ -1663,7 +1663,7 @@ export default class DevServer {
|
||||
if (!match) {
|
||||
// If the dev command is started, then proxy to it
|
||||
if (this.devProcessPort) {
|
||||
const upstream = `http://localhost:${this.devProcessPort}`;
|
||||
const upstream = `http://127.0.0.1:${this.devProcessPort}`;
|
||||
debug(`Proxying to frontend dev server: ${upstream}`);
|
||||
|
||||
// Add the Vercel platform proxy request headers
|
||||
@@ -1810,7 +1810,7 @@ export default class DevServer {
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${port}`,
|
||||
`http://127.0.0.1:${port}`,
|
||||
this,
|
||||
requestId,
|
||||
false
|
||||
@@ -1847,7 +1847,7 @@ export default class DevServer {
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
`http://127.0.0.1:${this.devProcessPort}`,
|
||||
this,
|
||||
requestId,
|
||||
false
|
||||
|
||||
6
packages/cli/test/fixtures/unit/edge-middleware-strict/_middleware.ts
vendored
Normal file
6
packages/cli/test/fixtures/unit/edge-middleware-strict/_middleware.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export default function (req) {
|
||||
const isStrict = (function () {
|
||||
return !this;
|
||||
})();
|
||||
return new Response('is strict mode? ' + (isStrict ? 'yes' : 'no'));
|
||||
}
|
||||
@@ -385,4 +385,13 @@ describe('DevServer', () => {
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
it(
|
||||
'should run middleware in strict mode',
|
||||
testFixture('edge-middleware-strict', async server => {
|
||||
const response = await fetch(`${server.address}/index.html`);
|
||||
const body = await response.text();
|
||||
expect(body).toStrictEqual('is strict mode? yes');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "10.2.3-canary.29",
|
||||
"version": "10.2.3-canary.39",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -40,7 +40,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.5.1-canary.16",
|
||||
"version": "0.5.1-canary.17",
|
||||
"main": "./dist/frameworks.js",
|
||||
"types": "./dist/frameworks.d.ts",
|
||||
"files": [
|
||||
|
||||
@@ -141,7 +141,6 @@ export const frameworks = [
|
||||
},
|
||||
dependency: 'gatsby',
|
||||
getOutputDirName: async () => 'public',
|
||||
getFsOutputDir: async () => 'public',
|
||||
defaultRoutes: async (dirPrefix: string) => {
|
||||
// This file could be generated by gatsby-plugin-now or gatsby-plugin-zeit-now
|
||||
try {
|
||||
@@ -226,7 +225,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'remix',
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -254,10 +252,13 @@ export const frameworks = [
|
||||
source: '/build/(.*)',
|
||||
regex: '/build/(.*)',
|
||||
headers: [
|
||||
{ key: 'cache-control', value: 'public, max-age=31536000, immutable' },
|
||||
{
|
||||
key: 'cache-control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Hexo',
|
||||
@@ -294,7 +295,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'hexo',
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
@@ -332,7 +332,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@11ty/eleventy',
|
||||
getFsOutputDir: async () => '_site',
|
||||
getOutputDirName: async () => '_site',
|
||||
cachePattern: '.cache/**',
|
||||
},
|
||||
@@ -372,22 +371,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@docusaurus/core',
|
||||
getFsOutputDir: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
try {
|
||||
const location = join(dirPrefix, base);
|
||||
const content = await readdir(location, { withFileTypes: true });
|
||||
|
||||
// If there is only one file in it that is a dir we'll use it as dist dir
|
||||
if (content.length === 1 && content[0].isDirectory()) {
|
||||
return join(base, content[0].name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error detecting output directory: `, error);
|
||||
}
|
||||
|
||||
return base;
|
||||
},
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
try {
|
||||
@@ -527,21 +510,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'docusaurus',
|
||||
getFsOutputDir: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
try {
|
||||
const location = join(dirPrefix, base);
|
||||
const content = await readdir(location, { withFileTypes: true });
|
||||
|
||||
// If there is only one file in it that is a dir we'll use it as dist dir
|
||||
if (content.length === 1 && content[0].isDirectory()) {
|
||||
return join(base, content[0].name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error detecting output directory: `, error);
|
||||
}
|
||||
return base;
|
||||
},
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
try {
|
||||
@@ -593,7 +561,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'preact-cli',
|
||||
getFsOutputDir: async () => 'build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -650,7 +617,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@dojo/cli',
|
||||
getFsOutputDir: async () => 'output/dist',
|
||||
getOutputDirName: async () => join('output', 'dist'),
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -717,7 +683,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'ember-cli',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -772,7 +737,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@vue/cli-service',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -849,7 +813,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@scullyio/init',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist/static',
|
||||
},
|
||||
{
|
||||
@@ -886,7 +849,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@ionic/angular',
|
||||
getFsOutputDir: async () => 'www',
|
||||
getOutputDirName: async () => 'www',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -940,7 +902,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@angular/cli',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'dist';
|
||||
try {
|
||||
@@ -1008,7 +969,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'polymer-cli',
|
||||
getFsOutputDir: async () => 'build',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
try {
|
||||
@@ -1078,7 +1038,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'sirv-cli',
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1128,10 +1087,9 @@ export const frameworks = [
|
||||
placeholder: 'svelte-kit dev',
|
||||
},
|
||||
outputDirectory: {
|
||||
placeholder: 'public',
|
||||
value: 'public',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async () => '.output',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
@@ -1168,7 +1126,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@ionic/react',
|
||||
getFsOutputDir: async () => 'build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1276,7 +1233,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'react-scripts',
|
||||
getFsOutputDir: async () => 'build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1378,7 +1334,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'gridsome',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
},
|
||||
{
|
||||
@@ -1416,7 +1371,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'umi',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1470,7 +1424,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'sapper',
|
||||
getFsOutputDir: async () => '__sapper__/export',
|
||||
getOutputDirName: async () => '__sapper__/export',
|
||||
},
|
||||
{
|
||||
@@ -1508,7 +1461,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'saber',
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1577,7 +1529,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: '@stencil/core',
|
||||
getFsOutputDir: async () => 'www',
|
||||
getOutputDirName: async () => 'www',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -1666,7 +1617,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'nuxt',
|
||||
getFsOutputDir: async () => '.output',
|
||||
getOutputDirName: async () => 'dist',
|
||||
cachePattern: '.nuxt/**',
|
||||
defaultRoutes: [
|
||||
@@ -1724,7 +1674,6 @@ export const frameworks = [
|
||||
placeholder: 'RedwoodJS default',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
@@ -1768,16 +1717,6 @@ export const frameworks = [
|
||||
placeholder: '`public` or `publishDir` from the `config` file',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
||||
type HugoConfig = { publishDir?: string };
|
||||
const config = await readConfigFile<HugoConfig>(
|
||||
['config.json', 'config.yaml', 'config.toml'].map(fileName => {
|
||||
return join(dirPrefix, fileName);
|
||||
})
|
||||
);
|
||||
|
||||
return (config && config.publishDir) || 'public';
|
||||
},
|
||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||
type HugoConfig = { publishDir?: string };
|
||||
const config = await readConfigFile<HugoConfig>(
|
||||
@@ -1822,13 +1761,6 @@ export const frameworks = [
|
||||
placeholder: '`_site` or `destination` from `_config.yml`',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
||||
type JekyllConfig = { destination?: string };
|
||||
const config = await readConfigFile<JekyllConfig>(
|
||||
join(dirPrefix, '_config.yml')
|
||||
);
|
||||
return (config && config.destination) || '_site';
|
||||
},
|
||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||
type JekyllConfig = { destination?: string };
|
||||
const config = await readConfigFile<JekyllConfig>(
|
||||
@@ -1870,7 +1802,6 @@ export const frameworks = [
|
||||
value: 'public',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
@@ -1905,7 +1836,6 @@ export const frameworks = [
|
||||
value: 'build',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async () => 'build',
|
||||
getOutputDirName: async () => 'build',
|
||||
cachePattern: '{vendor/bin,vendor/cache,vendor/bundle}/**',
|
||||
},
|
||||
@@ -1940,7 +1870,6 @@ export const frameworks = [
|
||||
value: 'public',
|
||||
},
|
||||
},
|
||||
getFsOutputDir: async () => 'public',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultVersion: '0.13.0',
|
||||
},
|
||||
@@ -1980,7 +1909,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'vite',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
},
|
||||
{
|
||||
@@ -2018,7 +1946,6 @@ export const frameworks = [
|
||||
},
|
||||
},
|
||||
dependency: 'parcel',
|
||||
getFsOutputDir: async () => 'dist',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
|
||||
@@ -162,9 +162,9 @@ export interface Framework {
|
||||
dependency?: string;
|
||||
/**
|
||||
* Function that returns the name of the directory that the framework outputs
|
||||
* its build results to. In some cases this is read from a configuration file.
|
||||
* its File System API build results to, usually called `.output`.
|
||||
*/
|
||||
getFsOutputDir: (dirPrefix: string) => Promise<string>;
|
||||
getFsOutputDir?: (dirPrefix: string) => Promise<string>;
|
||||
/**
|
||||
* Function that returns the name of the directory that the framework outputs
|
||||
* its STATIC build results to. In some cases this is read from a configuration file.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-middleware",
|
||||
"version": "0.0.0-canary.7",
|
||||
"version": "0.0.0-canary.14",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "",
|
||||
@@ -30,6 +30,7 @@
|
||||
"@types/node-fetch": "^2",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/uuid": "8.3.1",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"cookie": "0.4.1",
|
||||
"formdata-node": "4.3.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as middleware from './_middleware';
|
||||
import * as middleware from './_temp_middleware';
|
||||
_ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
|
||||
_ENTRIES['middleware_pages/_middleware'] = {
|
||||
default: async function (ev) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { promises as fsp } from 'fs';
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
import libGlob from 'glob';
|
||||
import Proxy from 'http-proxy';
|
||||
import { updateFunctionsManifest } from '@vercel/build-utils';
|
||||
|
||||
import { run } from './websandbox';
|
||||
import type { FetchEventResult } from './websandbox/types';
|
||||
@@ -22,7 +23,8 @@ const SUPPORTED_EXTENSIONS = ['.js', '.ts'];
|
||||
|
||||
// File name of the `entries.js` file that gets copied into the
|
||||
// project directory. Use a name that is unlikely to conflict.
|
||||
const ENTRIES_NAME = '___vc_entries.js';
|
||||
const TMP_ENTRIES_NAME = '.output/inputs/middleware/___vc_entries.js';
|
||||
const TMP_MIDDLEWARE_BUNDLE = '.output/inputs/middleware/_temp_middleware.js';
|
||||
|
||||
async function getMiddlewareFile(workingDirectory: string) {
|
||||
// Only the root-level `_middleware.*` files are considered.
|
||||
@@ -52,17 +54,36 @@ async function getMiddlewareFile(workingDirectory: string) {
|
||||
}
|
||||
|
||||
export async function build({ workPath }: { workPath: string }) {
|
||||
const entriesPath = join(workPath, ENTRIES_NAME);
|
||||
const entriesPath = join(workPath, TMP_ENTRIES_NAME);
|
||||
const transientFilePath = join(workPath, TMP_MIDDLEWARE_BUNDLE);
|
||||
const middlewareFile = await getMiddlewareFile(workPath);
|
||||
if (!middlewareFile) return;
|
||||
|
||||
console.log('Compiling middleware file: %j', middlewareFile);
|
||||
|
||||
// Create `_ENTRIES` wrapper
|
||||
await fsp.copyFile(join(__dirname, 'entries.js'), entriesPath);
|
||||
|
||||
// Build
|
||||
/**
|
||||
* Two builds happen here, because esbuild doesn't offer a way to add a banner
|
||||
* to individual input files, and the entries wrapper relies on running in
|
||||
* non-strict mode to access the ENTRIES global.
|
||||
*
|
||||
* To work around this, we bundle the middleware directly and add
|
||||
* 'use strict'; to make the entire bundle run in strict mode. We then bundle
|
||||
* a second time, adding the global ENTRIES wrapper and preserving the
|
||||
* 'use strict' for the entire scope of the original bundle.
|
||||
*/
|
||||
try {
|
||||
await esbuild.build({
|
||||
entryPoints: [middlewareFile],
|
||||
bundle: true,
|
||||
absWorkingDir: workPath,
|
||||
outfile: transientFilePath,
|
||||
banner: {
|
||||
js: '"use strict";',
|
||||
},
|
||||
format: 'cjs',
|
||||
});
|
||||
// Create `_ENTRIES` wrapper
|
||||
await fsp.copyFile(join(__dirname, 'entries.js'), entriesPath);
|
||||
await esbuild.build({
|
||||
entryPoints: [entriesPath],
|
||||
bundle: true,
|
||||
@@ -70,29 +91,24 @@ export async function build({ workPath }: { workPath: string }) {
|
||||
outfile: join(workPath, '.output/server/pages/_middleware.js'),
|
||||
});
|
||||
} finally {
|
||||
await fsp.unlink(transientFilePath);
|
||||
await fsp.unlink(entriesPath);
|
||||
}
|
||||
|
||||
// Write middleware manifest
|
||||
const middlewareManifest = {
|
||||
version: 1,
|
||||
sortedMiddleware: ['/'],
|
||||
middleware: {
|
||||
'/': {
|
||||
env: [],
|
||||
files: ['server/pages/_middleware.js'],
|
||||
name: 'pages/_middleware',
|
||||
page: '/',
|
||||
regexp: '^/.*$',
|
||||
},
|
||||
},
|
||||
const fileName = basename(middlewareFile);
|
||||
const pages: { [key: string]: any } = {};
|
||||
|
||||
pages[fileName] = {
|
||||
runtime: 'web',
|
||||
env: [],
|
||||
files: ['server/pages/_middleware.js'],
|
||||
name: 'pages/_middleware',
|
||||
page: '/',
|
||||
regexp: '^/.*$',
|
||||
sortingIndex: 1,
|
||||
};
|
||||
const middlewareManifestData = JSON.stringify(middlewareManifest, null, 2);
|
||||
const middlewareManifestPath = join(
|
||||
workPath,
|
||||
'.output/server/middleware-manifest.json'
|
||||
);
|
||||
await fsp.writeFile(middlewareManifestPath, middlewareManifestData);
|
||||
|
||||
await updateFunctionsManifest({ workPath, pages });
|
||||
}
|
||||
|
||||
const stringifyQuery = (req: IncomingMessage, query: ParsedUrlQuery) => {
|
||||
|
||||
@@ -114,6 +114,7 @@ export async function run(params: {
|
||||
const content = readFileSync(params.path, 'utf-8');
|
||||
const esBuildResult = esbuild.transformSync(content, {
|
||||
format: 'cjs',
|
||||
banner: '"use strict";',
|
||||
});
|
||||
const x = vm.runInNewContext(m.wrap(esBuildResult.code), cache.sandbox, {
|
||||
filename: params.path,
|
||||
@@ -163,6 +164,7 @@ function sandboxRequire(referrer: string, specifier: string) {
|
||||
|
||||
const transformOptions: esbuild.TransformOptions = {
|
||||
format: 'cjs',
|
||||
banner: '"use strict";',
|
||||
};
|
||||
if (extname(resolved) === '.json') {
|
||||
transformOptions.loader = 'json';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
exports[`build() should build simple middleware 1`] = `
|
||||
Object {
|
||||
"middleware": Object {
|
||||
"/": Object {
|
||||
"pages": Object {
|
||||
"_middleware.js": Object {
|
||||
"env": Array [],
|
||||
"files": Array [
|
||||
"server/pages/_middleware.js",
|
||||
@@ -11,11 +11,10 @@ Object {
|
||||
"name": "pages/_middleware",
|
||||
"page": "/",
|
||||
"regexp": "^/.*$",
|
||||
"runtime": "web",
|
||||
"sortingIndex": 1,
|
||||
},
|
||||
},
|
||||
"sortedMiddleware": Array [
|
||||
"/",
|
||||
],
|
||||
"version": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
52
packages/middleware/test/build.test.ts
vendored
52
packages/middleware/test/build.test.ts
vendored
@@ -3,6 +3,30 @@ import { promises as fsp } from 'fs';
|
||||
import { build } from '../src';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
const setupFixture = async (fixture: string) => {
|
||||
const fixturePath = join(__dirname, `fixtures/${fixture}`);
|
||||
await build({
|
||||
workPath: fixturePath,
|
||||
});
|
||||
|
||||
const functionsManifest = JSON.parse(
|
||||
await fsp.readFile(
|
||||
join(fixturePath, '.output/functions-manifest.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
|
||||
const outputFile = join(fixturePath, '.output/server/pages/_middleware.js');
|
||||
expect(await fsp.stat(outputFile)).toBeTruthy();
|
||||
require(outputFile);
|
||||
//@ts-ignore
|
||||
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
|
||||
return {
|
||||
middleware,
|
||||
functionsManifest,
|
||||
};
|
||||
};
|
||||
|
||||
describe('build()', () => {
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
@@ -15,25 +39,9 @@ describe('build()', () => {
|
||||
delete global._ENTRIES;
|
||||
});
|
||||
it('should build simple middleware', async () => {
|
||||
const fixture = join(__dirname, 'fixtures/simple');
|
||||
await build({
|
||||
workPath: fixture,
|
||||
});
|
||||
const { functionsManifest, middleware } = await setupFixture('simple');
|
||||
|
||||
const middlewareManifest = JSON.parse(
|
||||
await fsp.readFile(
|
||||
join(fixture, '.output/server/middleware-manifest.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
expect(middlewareManifest).toMatchSnapshot();
|
||||
|
||||
const outputFile = join(fixture, '.output/server/pages/_middleware.js');
|
||||
expect(await fsp.stat(outputFile)).toBeTruthy();
|
||||
|
||||
require(outputFile);
|
||||
//@ts-ignore
|
||||
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
|
||||
expect(functionsManifest).toMatchSnapshot();
|
||||
expect(typeof middleware).toStrictEqual('function');
|
||||
const handledResponse = await middleware({
|
||||
request: {
|
||||
@@ -54,4 +62,12 @@ describe('build()', () => {
|
||||
(unhandledResponse.response as Response).headers.get('x-middleware-next')
|
||||
).toEqual('1');
|
||||
});
|
||||
|
||||
it('should create a middleware that runs in strict mode', async () => {
|
||||
const { middleware } = await setupFixture('use-strict');
|
||||
const response = await middleware({
|
||||
request: {},
|
||||
});
|
||||
expect(String(response.response.body)).toEqual('is strict mode? yes');
|
||||
});
|
||||
});
|
||||
|
||||
6
packages/middleware/test/fixtures/use-strict/_middleware.js
vendored
Normal file
6
packages/middleware/test/fixtures/use-strict/_middleware.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export default function (req) {
|
||||
const isStrict = (function () {
|
||||
return !this;
|
||||
})();
|
||||
return new Response('is strict mode? ' + (isStrict ? 'yes' : 'no'));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-go",
|
||||
"version": "1.0.0-canary.13",
|
||||
"version": "1.0.0-canary.26",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,7 +17,7 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/go": "1.2.4-canary.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||
import * as go from '@vercel/go';
|
||||
import { name } from '../package.json';
|
||||
|
||||
export const build = convertRuntimeToPlugin(go.build, name, '.go');
|
||||
export const build = convertRuntimeToPlugin(go.build, 'vercel-plugin-go', '.go');
|
||||
|
||||
export const startDevServer = go.startDevServer;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"resolveJsonModule": true
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-node",
|
||||
"version": "1.12.2-canary.19",
|
||||
"version": "1.12.2-canary.30",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -34,12 +34,12 @@
|
||||
"@types/node-fetch": "2",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@types/yazl": "2.4.2",
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/fun": "1.0.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.14.0",
|
||||
"@vercel/node-bridge": "2.1.1-canary.2",
|
||||
"@vercel/static-config": "0.0.1-canary.0",
|
||||
"@vercel/static-config": "0.0.1-canary.1",
|
||||
"abort-controller": "3.0.0",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
|
||||
@@ -401,9 +401,9 @@ export async function build({ workPath }: { workPath: string }) {
|
||||
getConfig(project, absEntrypoint, FunctionConfigSchema) || {};
|
||||
|
||||
// No config exported means "node", but if there is a config
|
||||
// and "runtime" is defined, but it is not "node" then don't
|
||||
// and "use" is defined, but it is not "node" then don't
|
||||
// compile this file.
|
||||
if (config.runtime && config.runtime !== 'node') {
|
||||
if (config.use && config.use !== 'node') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-python",
|
||||
"version": "1.0.0-canary.14",
|
||||
"version": "1.0.0-canary.27",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,7 +17,7 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/python": "2.1.2-canary.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||
import * as python from '@vercel/python';
|
||||
import { name } from '../package.json';
|
||||
|
||||
export const build = convertRuntimeToPlugin(python.build, name, '.py');
|
||||
export const build = convertRuntimeToPlugin(python.build, 'vercel-plugin-python', '.py');
|
||||
|
||||
//export const startDevServer = python.startDevServer;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"outDir": "dist",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"resolveJsonModule": true
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-ruby",
|
||||
"version": "1.0.0-canary.12",
|
||||
"version": "1.0.0-canary.26",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.28",
|
||||
"@vercel/ruby": "1.2.8-canary.6"
|
||||
"@vercel/build-utils": "2.12.3-canary.38",
|
||||
"@vercel/ruby": "1.2.10-canary.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||
import * as ruby from '@vercel/ruby';
|
||||
import { name } from '../package.json';
|
||||
|
||||
export const build = convertRuntimeToPlugin(ruby.build, name, '.rb');
|
||||
export const build = convertRuntimeToPlugin(ruby.build, 'vercel-plugin-ruby', '.rb');
|
||||
|
||||
//export const startDevServer = ruby.startDevServer;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"outDir": "dist",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"resolveJsonModule": true
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,24 @@ interface RubyVersion extends NodeVersion {
|
||||
|
||||
const allOptions: RubyVersion[] = [
|
||||
{ major: 2, minor: 7, range: '2.7.x', runtime: 'ruby2.7' },
|
||||
{ major: 2, minor: 5, range: '2.5.x', runtime: 'ruby2.5' },
|
||||
{
|
||||
major: 2,
|
||||
minor: 5,
|
||||
range: '2.5.x',
|
||||
runtime: 'ruby2.5',
|
||||
discontinueDate: new Date('2021-11-30'),
|
||||
},
|
||||
];
|
||||
|
||||
function getLatestRubyVersion(): RubyVersion {
|
||||
return allOptions[0];
|
||||
}
|
||||
|
||||
function isDiscontinued({ discontinueDate }: RubyVersion): boolean {
|
||||
const today = Date.now();
|
||||
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
|
||||
}
|
||||
|
||||
function getRubyPath(meta: Meta, gemfileContents: string) {
|
||||
let selection = getLatestRubyVersion();
|
||||
if (meta.isDev) {
|
||||
@@ -37,8 +48,20 @@ function getRubyPath(meta: Meta, gemfileContents: string) {
|
||||
if (!found) {
|
||||
throw new NowBuildError({
|
||||
code: 'RUBY_INVALID_VERSION',
|
||||
message: 'Found `Gemfile` with invalid Ruby version: `' + line + '`.',
|
||||
link: 'https://vercel.com/docs/runtimes#official-runtimes/ruby/ruby-version',
|
||||
message: `Found \`Gemfile\` with invalid Ruby version: \`${line}.\``,
|
||||
link: 'http://vercel.link/ruby-version',
|
||||
});
|
||||
}
|
||||
if (isDiscontinued(selection)) {
|
||||
const latest = getLatestRubyVersion();
|
||||
const intro = `Found \`Gemfile\` with discontinued Ruby version: \`${line}.\``;
|
||||
const hint = `Please set \`ruby "~> ${latest.range}"\` in your \`Gemfile\` to use Ruby ${latest.range}.`;
|
||||
const upstream =
|
||||
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
|
||||
throw new NowBuildError({
|
||||
code: 'RUBY_DISCONTINUED_VERSION',
|
||||
link: 'http://vercel.link/ruby-version',
|
||||
message: `${intro} ${hint} ${upstream}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@vercel/ruby",
|
||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||
"version": "1.2.8-canary.6",
|
||||
"version": "1.2.10-canary.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }],
|
||||
"build": { "env": { "RUBY_VERSION": "2.7.x" } },
|
||||
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "project/index.rb", "use": "@vercel/ruby" }],
|
||||
"build": { "env": { "RUBY_VERSION": "2.7.x" } },
|
||||
"probes": [
|
||||
{ "path": "/project/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }
|
||||
]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "index.ru", "use": "@vercel/ruby" }],
|
||||
"build": { "env": { "RUBY_VERSION": "2.7.x" } },
|
||||
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
|
||||
}
|
||||
|
||||
7
packages/ruby/test/fixtures/11-version-2-5-error/Gemfile
vendored
Normal file
7
packages/ruby/test/fixtures/11-version-2-5-error/Gemfile
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
ruby "~> 2.5.x"
|
||||
|
||||
gem "cowsay", "~> 0.3.0"
|
||||
16
packages/ruby/test/fixtures/11-version-2-5-error/Gemfile.lock
vendored
Normal file
16
packages/ruby/test/fixtures/11-version-2-5-error/Gemfile.lock
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
cowsay (0.3.0)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
cowsay (~> 0.3.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.5.5p157
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.22
|
||||
9
packages/ruby/test/fixtures/11-version-2-5-error/index.rb
vendored
Normal file
9
packages/ruby/test/fixtures/11-version-2-5-error/index.rb
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
require 'cowsay'
|
||||
|
||||
Handler = Proc.new do |req, res|
|
||||
name = req.query['name'] || 'World'
|
||||
|
||||
res.status = 200
|
||||
res['Content-Type'] = 'text/text; charset=utf-8'
|
||||
res.body = Cowsay.say("Hello #{name}", 'cow')
|
||||
end
|
||||
4
packages/ruby/test/fixtures/11-version-2-5-error/vercel.json
vendored
Normal file
4
packages/ruby/test/fixtures/11-version-2-5-error/vercel.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }]
|
||||
}
|
||||
24
packages/ruby/test/test.js
vendored
24
packages/ruby/test/test.js
vendored
@@ -23,8 +23,32 @@ beforeAll(async () => {
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
const testsThatFailToBuild = new Map([
|
||||
[
|
||||
'11-version-2-5-error',
|
||||
'Found `Gemfile` with discontinued Ruby version: `ruby "~> 2.5.x".` Please set `ruby "~> 2.7.x"` in your `Gemfile` to use Ruby 2.7.x. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
|
||||
],
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||
const errMsg = testsThatFailToBuild.get(fixture);
|
||||
if (errMsg) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`should fail to build ${fixture}`, async () => {
|
||||
try {
|
||||
await testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
path.join(fixturesPath, fixture)
|
||||
);
|
||||
} catch (err) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.deployment).toBeTruthy();
|
||||
expect(err.deployment.errorMessage).toBe(errMsg);
|
||||
}
|
||||
});
|
||||
continue; //eslint-disable-line
|
||||
}
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`should build ${fixture}`, async () => {
|
||||
await expect(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/static-config",
|
||||
"version": "0.0.1-canary.0",
|
||||
"version": "0.0.1-canary.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"repository": {
|
||||
|
||||
@@ -15,7 +15,7 @@ const ajv = new Ajv();
|
||||
export const BaseFunctionConfigSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
runtime: { type: 'string' },
|
||||
use: { type: 'string' },
|
||||
memory: { type: 'number' },
|
||||
maxDuration: { type: 'number' },
|
||||
regions: {
|
||||
|
||||
2
packages/static-config/test/fixtures/deno.ts
vendored
2
packages/static-config/test/fixtures/deno.ts
vendored
@@ -2,7 +2,7 @@ import ms from 'https://denopkg.com/TooTallNate/ms';
|
||||
import { readerFromStreamReader } from 'https://deno.land/std@0.107.0/io/streams.ts';
|
||||
|
||||
export const config = {
|
||||
runtime: 'deno',
|
||||
use: 'deno',
|
||||
location: 'https://example.com/page',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const config = {
|
||||
runtime: 0,
|
||||
use: 0,
|
||||
};
|
||||
|
||||
2
packages/static-config/test/fixtures/node.js
vendored
2
packages/static-config/test/fixtures/node.js
vendored
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs';
|
||||
|
||||
export const config = {
|
||||
runtime: 'node',
|
||||
use: 'node',
|
||||
memory: 1024,
|
||||
};
|
||||
|
||||
|
||||
4
packages/static-config/test/index.test.ts
vendored
4
packages/static-config/test/index.test.ts
vendored
@@ -10,7 +10,7 @@ describe('getConfig()', () => {
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"memory": 1024,
|
||||
"runtime": "node",
|
||||
"use": "node",
|
||||
}
|
||||
`);
|
||||
});
|
||||
@@ -27,7 +27,7 @@ describe('getConfig()', () => {
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"location": "https://example.com/page",
|
||||
"runtime": "deno",
|
||||
"use": "deno",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user