[go] Go builder improvements (#9576)

This PR fixes a handful of Go builder issues all related to the selected Golang version being used to build the function:

- `go.mod` version ignored for `vc build` and `vc dev`, uses system `PATH` version only
- `vc dev` fails if `go.mod` does not exist
- If the analyze bin doesn’t exist, downloads golang into `.vercel/cache/golang` instead of a global shared dir
- When running `vc dev`, doesn’t reuse go build code/common settings
- go tidy fails when `go.mod` set to 1.19 or 1.20, but 1.18 or older is installed
- `vc build` builds wrong arch on Apple Silicon/arm64
- `vc build` on Windows doesn't properly resolve "builds" in `vercel.json` due to posix separator issue
- `vc build` on Windows fails with `package <pkg/name> is not in GOROOT` due to posix separator issue
- Removed `actions/setup-go` from all test workflows

I added a test that tests the `go tidy` issue.
This commit is contained in:
Chris Barber
2023-03-16 14:05:09 -05:00
committed by GitHub
parent 4b657debed
commit 0dd3711f63
12 changed files with 383 additions and 148 deletions

View File

@@ -31,9 +31,6 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}

View File

@@ -29,9 +29,6 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
@@ -66,9 +63,6 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}

View File

@@ -1,7 +1,7 @@
import fs from 'fs-extra';
import chalk from 'chalk';
import dotenv from 'dotenv';
import { join, normalize, relative, resolve } from 'path';
import { join, normalize, relative, resolve, sep } from 'path';
import {
getDiscontinuedNodeVersions,
normalizePath,
@@ -712,7 +712,9 @@ function expandBuild(files: string[], build: Builder): Builder[] {
});
}
let src = normalize(build.src || '**');
let src = normalize(build.src || '**')
.split(sep)
.join('/');
if (src === '.' || src === './') {
throw new NowBuildError({
code: `invalid_build_specification`,

View File

@@ -1,16 +1,32 @@
import tar from 'tar';
import execa from 'execa';
import fetch from 'node-fetch';
import { mkdirp, pathExists, readFile } from 'fs-extra';
import { join, delimiter } from 'path';
import {
createWriteStream,
mkdirp,
pathExists,
readFile,
remove,
symlink,
} from 'fs-extra';
import { join, delimiter, dirname } from 'path';
import stringArgv from 'string-argv';
import { debug } from '@vercel/build-utils';
import { cloneEnv, debug } from '@vercel/build-utils';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { tmpdir } from 'os';
import yauzl from 'yauzl-promise';
import XDGAppPaths from 'xdg-app-paths';
import type { Env } from '@vercel/build-utils';
const streamPipeline = promisify(pipeline);
const versionMap = new Map([
['1.19', '1.19.5'],
['1.18', '1.18.1'],
['1.17', '1.17.3'],
['1.16', '1.16.10'],
['1.15', '1.15.8'],
['1.19', '1.19.6'],
['1.18', '1.18.10'],
['1.17', '1.17.13'],
['1.16', '1.16.15'],
['1.15', '1.15.15'],
['1.14', '1.14.15'],
['1.13', '1.13.15'],
]);
@@ -20,17 +36,27 @@ const archMap = new Map([
]);
const platformMap = new Map([['win32', 'windows']]);
export const cacheDir = join('.vercel', 'cache', 'golang');
const getGoDir = (workPath: string) => join(workPath, cacheDir);
const GO_FLAGS = process.platform === 'win32' ? [] : ['-ldflags', '-s -w'];
const GO_MIN_VERSION = 13;
const getPlatform = (p: string) => platformMap.get(p) || p;
const getArch = (a: string) => archMap.get(a) || a;
const getGoUrl = (version: string, platform: string, arch: string) => {
function getGoUrl(version: string) {
const { arch, platform } = process;
const goArch = getArch(arch);
const goPlatform = getPlatform(platform);
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
};
const filename = `go${version}.${goPlatform}-${goArch}.${ext}`;
return {
filename,
url: `https://dl.google.com/go/${filename}`,
};
}
export const goGlobalCachePath = join(
XDGAppPaths('com.vercel.cli').cache(),
'golang'
);
export const OUT_EXTENSION = process.platform === 'win32' ? '.exe' : '';
@@ -39,39 +65,31 @@ export async function getAnalyzedEntrypoint(
filePath: string,
modulePath: string
) {
debug('Analyzing entrypoint %o with modulePath %o', filePath, modulePath);
const bin = join(__dirname, `analyze${OUT_EXTENSION}`);
const isAnalyzeExist = await pathExists(bin);
if (!isAnalyzeExist) {
debug(`Building analyze bin: ${bin}`);
const src = join(__dirname, 'util', 'analyze.go');
const go = await downloadGo(workPath, modulePath);
const go = await createGo({
modulePath,
workPath,
});
await go.build(src, bin);
}
debug(`Analyzing entrypoint ${filePath} with modulePath ${modulePath}`);
const args = [`-modpath=${modulePath}`, filePath];
const analyzed = await execa.stdout(bin, args);
debug('Analyzed entrypoint %o', analyzed);
debug(`Analyzed entrypoint ${analyzed}`);
return analyzed;
}
// Creates a `$GOPATH` directory tree, as per `go help gopath` instructions.
// Without this, `go` won't recognize the `$GOPATH`.
function createGoPathTree(goPath: string, platform: string, arch: string) {
const tuple = `${getPlatform(platform)}_${getArch(arch)}`;
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
return Promise.all([
mkdirp(join(goPath, 'bin')),
mkdirp(join(goPath, 'pkg', tuple)),
]);
}
class GoWrapper {
private env: { [key: string]: string };
private env: Env;
private opts: execa.Options;
constructor(env: { [key: string]: string }, opts: execa.Options = {}) {
constructor(env: Env, opts: execa.Options = {}) {
if (!opts.cwd) {
opts.cwd = process.cwd();
}
@@ -81,8 +99,12 @@ class GoWrapper {
private execute(...args: string[]) {
const { opts, env } = this;
debug('Exec %o', `go ${args.join(' ')}`);
return execa('go', args, { stdio: 'pipe', ...opts, env });
debug(
`Exec: go ${args
.map(a => (a.includes(' ') ? `"${a}"` : a))
.join(' ')} CWD=${opts.cwd}`
);
return execa('go', args, { stdio: 'inherit', ...opts, env });
}
mod() {
@@ -92,16 +114,16 @@ class GoWrapper {
get(src?: string) {
const args = ['get'];
if (src) {
debug('Fetching `go` dependencies for file %o', src);
debug(`Fetching 'go' dependencies for file ${src}`);
args.push(src);
} else {
debug('Fetching `go` dependencies for cwd %o', this.opts.cwd);
debug(`Fetching 'go' dependencies for cwd ${this.opts.cwd}`);
}
return this.execute(...args);
}
build(src: string | string[], dest: string) {
debug('Building optimized `go` binary %o -> %o', src, dest);
debug(`Building optimized 'go' binary ${src} -> ${dest}`);
const sources = Array.isArray(src) ? src : [src];
const flags = process.env.GO_BUILD_FLAGS
@@ -112,72 +134,222 @@ class GoWrapper {
}
}
export async function createGo(
workPath: string,
goPath: string,
platform = process.platform,
arch = process.arch,
opts: execa.Options = {},
goMod = false
) {
const binPath = join(getGoDir(workPath), 'bin');
debug(`Adding ${binPath} to PATH`);
const path = `${binPath}${delimiter}${process.env.PATH}`;
const env: { [key: string]: string } = {
...process.env,
PATH: path,
GOPATH: goPath,
...opts.env,
};
if (goMod) {
env.GO111MODULE = 'on';
type CreateGoOptions = {
modulePath?: string;
opts?: execa.Options;
workPath: string;
};
/**
* Initializes a `GoWrapper` instance.
*
* This function determines the Go version to use by first looking in the
* `go.mod`, if exists, otherwise uses the latest version from the version
* map.
*
* Next it will attempt to find the desired Go version by checking the
* following locations:
* 1. The "local" project cache directory (e.g. `.vercel/cache/golang`)
* 2. The "global" cache directory (e.g. `~/.cache/com.vercel.com/golang`)
* 3. The system PATH
*
* If the Go version is not found, it's downloaded and installed in the
* global cache directory so it can be shared across projects. When using
* Linux or macOS, it creates a symlink from the global cache to the local
* cache directory so that `prepareCache` will persist it.
*
* @param modulePath The path possibly containing a `go.mod` file
* @param opts `execa` options (`cwd`, `env`, `stdio`, etc)
* @param workPath The path to the project to be built
* @returns An initialized `GoWrapper` instance
*/
export async function createGo({
modulePath,
opts = {},
workPath,
}: CreateGoOptions): Promise<GoWrapper> {
// parse the `go.mod`, if exists
let goPreferredVersion: string | undefined;
if (modulePath) {
goPreferredVersion = await parseGoModVersion(modulePath);
}
await createGoPathTree(goPath, platform, arch);
// default to newest (first) supported go version
const goSelectedVersion =
goPreferredVersion || Array.from(versionMap.values())[0];
const env = cloneEnv(process.env, opts.env);
const { PATH } = env;
const { platform } = process;
const goGlobalCacheDir = join(
goGlobalCachePath,
`${goSelectedVersion}_${platform}_${process.arch}`
);
const goCacheDir = join(workPath, cacheDir);
if (goPreferredVersion) {
debug(`Preferred go version ${goPreferredVersion} (from go.mod)`);
env.GO111MODULE = 'on';
} else {
debug(
`Preferred go version ${goSelectedVersion} (latest from version map)`
);
}
const setGoEnv = async (goDir: string | null) => {
if (platform !== 'win32' && goDir === goGlobalCacheDir) {
debug(`Symlinking ${goDir} -> ${goCacheDir}`);
await remove(goCacheDir);
await mkdirp(dirname(goCacheDir));
await symlink(goDir, goCacheDir);
goDir = goCacheDir;
}
env.GOROOT = goDir || undefined;
env.PATH = goDir ? `${join(goDir, 'bin')}${delimiter}${PATH}` : PATH;
};
// try each of these Go directories looking for the version we need
const goDirs = {
'local cache': goCacheDir,
'global cache': goGlobalCacheDir,
'system PATH': null,
};
for (const [label, goDir] of Object.entries(goDirs)) {
try {
const goBinDir = goDir && join(goDir, 'bin');
if (goBinDir && !(await pathExists(goBinDir))) {
debug(`Go not found in ${label}`);
continue;
}
env.GOROOT = goDir || undefined;
env.PATH = goBinDir || PATH;
const { stdout } = await execa('go', ['version'], { env });
const { minor, short, version } = parseGoVersionString(stdout);
if (minor < GO_MIN_VERSION) {
debug(`Found go ${version} in ${label}, but version is unsupported`);
}
if (version === goSelectedVersion || short === goSelectedVersion) {
console.log(`Selected go ${version} (from ${label})`);
await setGoEnv(goDir);
return new GoWrapper(env, opts);
} else {
debug(`Found go ${version} in ${label}, but need ${goSelectedVersion}`);
}
} catch {
debug(`Go not found in ${label}`);
}
}
// we need to download and cache the desired `go` version
await download({
dest: goGlobalCacheDir,
version: goSelectedVersion,
});
await setGoEnv(goGlobalCacheDir);
return new GoWrapper(env, opts);
}
export async function downloadGo(workPath: string, modulePath: string) {
const dir = getGoDir(workPath);
const { platform, arch } = process;
const version = await parseGoVersion(modulePath);
/**
* Download and installs the Go distribution.
*
* @param dest The directory to install Go into. If directory exists, it is
* first deleted before installing.
* @param version The Go version to download
*/
async function download({ dest, version }: { dest: string; version: string }) {
const { filename, url } = getGoUrl(version);
console.log(`Downloading go: ${url}`);
const res = await fetch(url);
// Check if `go` is already installed in user's `$PATH`
const { failed, stdout } = await execa('go', ['version'], { reject: false });
if (!failed && parseInt(stdout.split('.')[1]) >= GO_MIN_VERSION) {
debug('Using system installed version of `go`: %o', stdout.trim());
return createGo(workPath, dir, platform, arch);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
}
// Check `go` bin in cacheDir
const isGoExist = await pathExists(join(dir, 'bin'));
if (!isGoExist) {
debug('Installing `go` v%s to %o for %s %s', version, dir, platform, arch);
const url = getGoUrl(version, platform, arch);
debug('Downloading `go` URL: %o', url);
const res = await fetch(url);
debug(`Installing go ${version} to ${dest}`);
if (!res.ok) {
throw new Error(`Failed to download: ${url} (${res.status})`);
await remove(dest);
await mkdirp(dest);
if (/\.zip$/.test(filename)) {
const zipFile = join(tmpdir(), filename);
try {
await streamPipeline(res.body, createWriteStream(zipFile));
const zip = await yauzl.open(zipFile);
let entry = await zip.readEntry();
while (entry) {
const fileName = entry.fileName.split('/').slice(1).join('/');
if (fileName) {
const destPath = join(dest, fileName);
if (/\/$/.test(fileName)) {
await mkdirp(destPath);
} else {
const [entryStream] = await Promise.all([
entry.openReadStream(),
mkdirp(dirname(destPath)),
]);
const out = createWriteStream(destPath);
await streamPipeline(entryStream, out);
}
}
entry = await zip.readEntry();
}
} finally {
await remove(zipFile);
}
// TODO: use a zip extractor when `ext === "zip"`
await mkdirp(dir);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dir, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
return;
}
return createGo(workPath, dir, platform, arch);
await new Promise((resolve, reject) => {
res.body
.on('error', reject)
.pipe(tar.extract({ cwd: dest, strip: 1 }))
.on('error', reject)
.on('finish', resolve);
});
}
async function parseGoVersion(modulePath: string): Promise<string> {
// default to newest (first)
let version = Array.from(versionMap.values())[0];
const goVersionRegExp = /(\d+)\.(\d+)(?:\.(\d+))?/;
/**
* Parses the raw output from `go version` and returns the version parts.
*
* @param goVersionOutput The output from `go version`
*/
function parseGoVersionString(goVersionOutput: string) {
const matches = goVersionOutput.match(goVersionRegExp) || [];
const major = parseInt(matches[1], 10);
const minor = parseInt(matches[2], 10);
const patch = parseInt(matches[3] || '0', 10);
return {
version: `${major}.${minor}.${patch}`,
short: `${major}.${minor}`,
major,
minor,
patch,
};
}
/**
* Attempts to parse the preferred Go version from the `go.mod` file.
*
* @param modulePath The directory containing the `go.mod` file
* @returns
*/
async function parseGoModVersion(
modulePath: string
): Promise<string | undefined> {
let version;
const file = join(modulePath, 'go.mod');
try {
const content = await readFile(file, 'utf8');
const matches = /^go (\d+)\.(\d+)\.?$/gm.exec(content) || [];
@@ -189,7 +361,7 @@ async function parseGoVersion(modulePath: string): Promise<string> {
} else {
console.log(`Warning: Unknown Go version in ${file}`);
}
} catch (err) {
} catch (err: any) {
if (err.code === 'ENOENT') {
debug(`File not found: ${file}`);
} else {
@@ -197,6 +369,5 @@ async function parseGoVersion(modulePath: string): Promise<string> {
}
}
debug(`Selected Go version ${version}`);
return version;
}

View File

@@ -1,19 +1,22 @@
import execa from 'execa';
import retry from 'async-retry';
import { homedir, tmpdir } from 'os';
import { execFileSync, spawn } from 'child_process';
import { spawn } from 'child_process';
import { Readable } from 'stream';
import once from '@tootallnate/once';
import { join, dirname, basename, normalize, sep } from 'path';
import { join, dirname, basename, normalize, posix, sep } from 'path';
import {
readFile,
writeFile,
lstat,
pathExists,
mkdirp,
move,
readlink,
remove,
rmdir,
readdir,
unlink,
} from 'fs-extra';
import {
BuildOptions,
@@ -33,19 +36,20 @@ import {
const TMP = tmpdir();
import {
cacheDir,
createGo,
getAnalyzedEntrypoint,
cacheDir,
OUT_EXTENSION,
} from './go-helpers';
const handlerFileName = `handler${OUT_EXTENSION}`;
export { shouldServe };
interface Analyzed {
found?: boolean;
packageName: string;
functionName: string;
packageName: string;
watch?: boolean;
}
interface PortInfo {
@@ -147,7 +151,7 @@ export async function build({
}
const entrypointAbsolute = join(workPath, entrypoint);
const entrypointArr = entrypoint.split(sep);
const entrypointArr = entrypoint.split(posix.sep);
debug(`Parsing AST for "${entrypoint}"`);
let analyzed: string;
@@ -163,7 +167,7 @@ export async function build({
dirname(goModAbsPath)
);
} catch (err) {
console.log(`Failed to parse AST for "${entrypoint}"`);
console.error(`Failed to parse AST for "${entrypoint}"`);
throw err;
}
@@ -173,7 +177,7 @@ export async function build({
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
`
);
console.log(err.message);
console.error(err.message);
throw err;
}
@@ -289,18 +293,19 @@ export async function build({
// we need our `main.go` to be called something else
const mainGoFileName = 'main__vc__go__.go';
if (packageName !== 'main') {
const go = await createGo(
workPath,
goPath,
process.platform,
process.arch,
{
cwd: entrypointDirname,
stdio: 'inherit',
const go = await createGo({
modulePath: goModPath,
opts: {
cwd: entrypointDirname,
env: {
GOARCH: 'amd64',
GOOS: 'linux',
},
true
);
},
workPath,
});
if (packageName !== 'main') {
if (!isGoModExist) {
try {
const defaultGoModContent = `module ${packageName}`;
@@ -321,7 +326,7 @@ export async function build({
from: join(entrypointDirname, 'go.sum'),
});
} catch (err) {
console.log(`Failed to create default go.mod for ${packageName}`);
console.error(`Failed to create default go.mod for ${packageName}`);
throw err;
}
}
@@ -353,6 +358,7 @@ export async function build({
if (isGoModExist && isGoModInRootDir) {
debug('[mod-root] Write main file to ' + downloadPath);
await writeFile(join(downloadPath, mainGoFileName), mainModGoContents);
undoFileActions.push({
to: undefined, // delete
from: join(downloadPath, mainGoFileName),
@@ -403,7 +409,7 @@ export async function build({
undoDirectoryCreation.push(dirname(finalDestination));
}
} catch (err) {
console.log('Failed to move entry to package folder');
console.error('Failed to move entry to package folder');
throw err;
}
@@ -421,7 +427,7 @@ export async function build({
// ensure go.mod up-to-date
await go.mod();
} catch (err) {
console.log('failed to `go mod tidy`');
console.error('failed to `go mod tidy`');
throw err;
}
@@ -433,23 +439,13 @@ export async function build({
await go.build(src, destPath);
} catch (err) {
console.log('failed to `go build`');
console.error('failed to `go build`');
throw err;
}
} else {
// legacy mode
// we need `main.go` in the same dir as the entrypoint,
// otherwise `go build` will refuse to build
const go = await createGo(
workPath,
goPath,
process.platform,
process.arch,
{
cwd: entrypointDirname,
},
false
);
const originalMainGoContents = await readFile(
join(__dirname, 'main.go'),
'utf8'
@@ -472,7 +468,7 @@ export async function build({
try {
await go.get();
} catch (err) {
console.log('Failed to `go get`');
console.error('Failed to `go get`');
throw err;
}
@@ -485,7 +481,7 @@ export async function build({
].map(file => normalize(file));
await go.build(src, destPath);
} catch (err) {
console.log('failed to `go build`');
console.error('failed to `go build`');
throw err;
}
}
@@ -513,7 +509,9 @@ export async function build({
undoFunctionRenames
);
} catch (error) {
console.log(`Build cleanup failed: ${error.message}`);
if (error instanceof Error) {
console.error(`Build cleanup failed: ${error.message}`);
}
debug('Cleanup Error: ' + error);
}
}
@@ -640,6 +638,19 @@ async function copyDevServer(
await writeFile(join(dest, 'vercel-dev-server-main.go'), patched);
}
async function writeDefaultGoMod(
entrypointDirname: string,
packageName: string
) {
const defaultGoModContent = `module ${packageName}`;
await writeFile(
join(entrypointDirname, 'go.mod'),
defaultGoModContent,
'utf-8'
);
}
export async function startDevServer(
opts: StartDevServerOptions
): Promise<StartDevServerResult> {
@@ -678,6 +689,7 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
await Promise.all([
copyEntrypoint(entrypointWithExt, tmpPackage),
copyDevServer(analyzed.functionName, tmpPackage),
goModAbsPathDir ? null : writeDefaultGoMod(tmp, analyzed.packageName),
]);
const portFile = join(
@@ -693,13 +705,22 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
process.platform === 'win32' ? '.exe' : ''
}`;
debug(`SPAWNING go build -o ${executable} ./... CWD=${tmp}`);
execFileSync('go', ['build', '-o', executable, './...'], {
cwd: tmp,
env,
stdio: 'inherit',
});
// Note: We must run `go build`, then manually spawn the dev server instead
// of spawning `go run`. See https://github.com/vercel/vercel/pull/8718 for
// more info.
// build the dev server
const go = await createGo({
modulePath: goModAbsPathDir,
opts: {
cwd: tmp,
env,
},
workPath,
});
await go.build('./...', executable);
// run the dev server
debug(`SPAWNING ${executable} CWD=${tmp}`);
const child = spawn(executable, [], {
cwd: tmp,
@@ -770,10 +791,10 @@ async function waitForPortFile_(opts: {
try {
const port = Number(await readFile(opts.portFile, 'ascii'));
retry(() => remove(opts.portFile)).catch((err: Error) => {
console.error('Could not delete port file: %j: %s', opts.portFile, err);
console.error(`Could not delete port file: ${opts.portFile}: ${err}`);
});
return { port };
} catch (err) {
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw err;
}
@@ -784,6 +805,25 @@ async function waitForPortFile_(opts: {
export async function prepareCache({
workPath,
}: PrepareCacheOptions): Promise<Files> {
// When building the project for the first time, there won't be a cache and
// `createGo()` will have downloaded Go to the global cache directory, then
// symlinked it to the local `cacheDir`.
//
// If we detect the `cacheDir` is a symlink, unlink it, then move the global
// cache directory into the local cache directory so that it can be
// persisted.
//
// On the next build, the local cache will be restored and `createGo()` will
// use it unless the preferred Go version changed in the `go.mod`.
const goCacheDir = join(workPath, cacheDir);
const stat = await lstat(goCacheDir);
if (stat.isSymbolicLink()) {
const goGlobalCacheDir = await readlink(goCacheDir);
debug(`Preparing cache by moving ${goGlobalCacheDir} -> ${goCacheDir}`);
await unlink(goCacheDir);
await move(goGlobalCacheDir, goCacheDir);
}
const cache = await glob(`${cacheDir}/**`, workPath);
return cache;
}

View File

@@ -36,6 +36,7 @@
"@types/node": "14.18.33",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@types/yauzl-promise": "2.1.0",
"@vercel/build-utils": "6.3.4",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
@@ -44,6 +45,8 @@
"node-fetch": "^2.2.1",
"string-argv": "0.3.1",
"tar": "4.4.6",
"typescript": "4.3.4"
"typescript": "4.3.4",
"xdg-app-paths": "5.1.0",
"yauzl-promise": "2.1.3"
}
}

View File

@@ -5,10 +5,10 @@
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
],
"probes": [
{ "path": "/", "mustContain": "cow:go1.19.5:RANDOMNESS_PLACEHOLDER" },
{ "path": "/", "mustContain": "cow:go1.19.6:RANDOMNESS_PLACEHOLDER" },
{
"path": "/subdirectory",
"mustContain": "subcow:go1.19.5:RANDOMNESS_PLACEHOLDER"
"mustContain": "subcow:go1.19.6:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -0,0 +1,3 @@
module go-mod
go 1.19

View File

@@ -0,0 +1,12 @@
package handler
import (
"fmt"
"net/http"
"runtime"
)
// Handler func
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "version:%s:RANDOMNESS_PLACEHOLDER", runtime.Version())
}

View File

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

View File

@@ -2,6 +2,6 @@
"version": 2,
"builds": [{ "src": "index.go", "use": "@vercel/go" }],
"probes": [
{ "path": "/", "mustContain": "version:go1.15.8:RANDOMNESS_PLACEHOLDER" }
{ "path": "/", "mustContain": "version:go1.15.15:RANDOMNESS_PLACEHOLDER" }
]
}

6
pnpm-lock.yaml generated
View File

@@ -661,6 +661,7 @@ importers:
'@types/node': 14.18.33
'@types/node-fetch': ^2.3.0
'@types/tar': ^4.0.0
'@types/yauzl-promise': 2.1.0
'@vercel/build-utils': 6.3.4
'@vercel/ncc': 0.24.0
async-retry: 1.3.1
@@ -670,6 +671,8 @@ importers:
string-argv: 0.3.1
tar: 4.4.6
typescript: 4.3.4
xdg-app-paths: 5.1.0
yauzl-promise: 2.1.3
devDependencies:
'@tootallnate/once': 1.1.2
'@types/async-retry': 1.4.2
@@ -679,6 +682,7 @@ importers:
'@types/node': 14.18.33
'@types/node-fetch': 2.6.2
'@types/tar': 4.0.5
'@types/yauzl-promise': 2.1.0
'@vercel/build-utils': link:../build-utils
'@vercel/ncc': 0.24.0
async-retry: 1.3.1
@@ -688,6 +692,8 @@ importers:
string-argv: 0.3.1
tar: 4.4.6
typescript: 4.3.4
xdg-app-paths: 5.1.0
yauzl-promise: 2.1.3
packages/hydrogen:
specifiers: