mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 04:22:12 +00:00
[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:
3
.github/workflows/test-integration-cli.yml
vendored
3
.github/workflows/test-integration-cli.yml
vendored
@@ -31,9 +31,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '1.18'
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -29,9 +29,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '1.13.15'
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
@@ -66,9 +63,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '1.13.15'
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { join, normalize, relative, resolve } from 'path';
|
import { join, normalize, relative, resolve, sep } from 'path';
|
||||||
import {
|
import {
|
||||||
getDiscontinuedNodeVersions,
|
getDiscontinuedNodeVersions,
|
||||||
normalizePath,
|
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 === './') {
|
if (src === '.' || src === './') {
|
||||||
throw new NowBuildError({
|
throw new NowBuildError({
|
||||||
code: `invalid_build_specification`,
|
code: `invalid_build_specification`,
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
import tar from 'tar';
|
import tar from 'tar';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { mkdirp, pathExists, readFile } from 'fs-extra';
|
import {
|
||||||
import { join, delimiter } from 'path';
|
createWriteStream,
|
||||||
|
mkdirp,
|
||||||
|
pathExists,
|
||||||
|
readFile,
|
||||||
|
remove,
|
||||||
|
symlink,
|
||||||
|
} from 'fs-extra';
|
||||||
|
import { join, delimiter, dirname } from 'path';
|
||||||
import stringArgv from 'string-argv';
|
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([
|
const versionMap = new Map([
|
||||||
['1.19', '1.19.5'],
|
['1.19', '1.19.6'],
|
||||||
['1.18', '1.18.1'],
|
['1.18', '1.18.10'],
|
||||||
['1.17', '1.17.3'],
|
['1.17', '1.17.13'],
|
||||||
['1.16', '1.16.10'],
|
['1.16', '1.16.15'],
|
||||||
['1.15', '1.15.8'],
|
['1.15', '1.15.15'],
|
||||||
['1.14', '1.14.15'],
|
['1.14', '1.14.15'],
|
||||||
['1.13', '1.13.15'],
|
['1.13', '1.13.15'],
|
||||||
]);
|
]);
|
||||||
@@ -20,17 +36,27 @@ const archMap = new Map([
|
|||||||
]);
|
]);
|
||||||
const platformMap = new Map([['win32', 'windows']]);
|
const platformMap = new Map([['win32', 'windows']]);
|
||||||
export const cacheDir = join('.vercel', 'cache', 'golang');
|
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_FLAGS = process.platform === 'win32' ? [] : ['-ldflags', '-s -w'];
|
||||||
const GO_MIN_VERSION = 13;
|
const GO_MIN_VERSION = 13;
|
||||||
const getPlatform = (p: string) => platformMap.get(p) || p;
|
const getPlatform = (p: string) => platformMap.get(p) || p;
|
||||||
const getArch = (a: string) => archMap.get(a) || a;
|
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 goArch = getArch(arch);
|
||||||
const goPlatform = getPlatform(platform);
|
const goPlatform = getPlatform(platform);
|
||||||
const ext = platform === 'win32' ? 'zip' : 'tar.gz';
|
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' : '';
|
export const OUT_EXTENSION = process.platform === 'win32' ? '.exe' : '';
|
||||||
|
|
||||||
@@ -39,39 +65,31 @@ export async function getAnalyzedEntrypoint(
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
modulePath: string
|
modulePath: string
|
||||||
) {
|
) {
|
||||||
debug('Analyzing entrypoint %o with modulePath %o', filePath, modulePath);
|
|
||||||
const bin = join(__dirname, `analyze${OUT_EXTENSION}`);
|
const bin = join(__dirname, `analyze${OUT_EXTENSION}`);
|
||||||
|
|
||||||
const isAnalyzeExist = await pathExists(bin);
|
const isAnalyzeExist = await pathExists(bin);
|
||||||
if (!isAnalyzeExist) {
|
if (!isAnalyzeExist) {
|
||||||
|
debug(`Building analyze bin: ${bin}`);
|
||||||
const src = join(__dirname, 'util', 'analyze.go');
|
const src = join(__dirname, 'util', 'analyze.go');
|
||||||
const go = await downloadGo(workPath, modulePath);
|
const go = await createGo({
|
||||||
|
modulePath,
|
||||||
|
workPath,
|
||||||
|
});
|
||||||
await go.build(src, bin);
|
await go.build(src, bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug(`Analyzing entrypoint ${filePath} with modulePath ${modulePath}`);
|
||||||
const args = [`-modpath=${modulePath}`, filePath];
|
const args = [`-modpath=${modulePath}`, filePath];
|
||||||
|
|
||||||
const analyzed = await execa.stdout(bin, args);
|
const analyzed = await execa.stdout(bin, args);
|
||||||
debug('Analyzed entrypoint %o', analyzed);
|
debug(`Analyzed entrypoint ${analyzed}`);
|
||||||
return 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 {
|
class GoWrapper {
|
||||||
private env: { [key: string]: string };
|
private env: Env;
|
||||||
private opts: execa.Options;
|
private opts: execa.Options;
|
||||||
|
|
||||||
constructor(env: { [key: string]: string }, opts: execa.Options = {}) {
|
constructor(env: Env, opts: execa.Options = {}) {
|
||||||
if (!opts.cwd) {
|
if (!opts.cwd) {
|
||||||
opts.cwd = process.cwd();
|
opts.cwd = process.cwd();
|
||||||
}
|
}
|
||||||
@@ -81,8 +99,12 @@ class GoWrapper {
|
|||||||
|
|
||||||
private execute(...args: string[]) {
|
private execute(...args: string[]) {
|
||||||
const { opts, env } = this;
|
const { opts, env } = this;
|
||||||
debug('Exec %o', `go ${args.join(' ')}`);
|
debug(
|
||||||
return execa('go', args, { stdio: 'pipe', ...opts, env });
|
`Exec: go ${args
|
||||||
|
.map(a => (a.includes(' ') ? `"${a}"` : a))
|
||||||
|
.join(' ')} CWD=${opts.cwd}`
|
||||||
|
);
|
||||||
|
return execa('go', args, { stdio: 'inherit', ...opts, env });
|
||||||
}
|
}
|
||||||
|
|
||||||
mod() {
|
mod() {
|
||||||
@@ -92,16 +114,16 @@ class GoWrapper {
|
|||||||
get(src?: string) {
|
get(src?: string) {
|
||||||
const args = ['get'];
|
const args = ['get'];
|
||||||
if (src) {
|
if (src) {
|
||||||
debug('Fetching `go` dependencies for file %o', src);
|
debug(`Fetching 'go' dependencies for file ${src}`);
|
||||||
args.push(src);
|
args.push(src);
|
||||||
} else {
|
} else {
|
||||||
debug('Fetching `go` dependencies for cwd %o', this.opts.cwd);
|
debug(`Fetching 'go' dependencies for cwd ${this.opts.cwd}`);
|
||||||
}
|
}
|
||||||
return this.execute(...args);
|
return this.execute(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
build(src: string | string[], dest: string) {
|
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 sources = Array.isArray(src) ? src : [src];
|
||||||
|
|
||||||
const flags = process.env.GO_BUILD_FLAGS
|
const flags = process.env.GO_BUILD_FLAGS
|
||||||
@@ -112,72 +134,222 @@ class GoWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createGo(
|
type CreateGoOptions = {
|
||||||
workPath: string,
|
modulePath?: string;
|
||||||
goPath: string,
|
opts?: execa.Options;
|
||||||
platform = process.platform,
|
workPath: string;
|
||||||
arch = process.arch,
|
};
|
||||||
opts: execa.Options = {},
|
|
||||||
goMod = false
|
/**
|
||||||
) {
|
* Initializes a `GoWrapper` instance.
|
||||||
const binPath = join(getGoDir(workPath), 'bin');
|
*
|
||||||
debug(`Adding ${binPath} to PATH`);
|
* This function determines the Go version to use by first looking in the
|
||||||
const path = `${binPath}${delimiter}${process.env.PATH}`;
|
* `go.mod`, if exists, otherwise uses the latest version from the version
|
||||||
const env: { [key: string]: string } = {
|
* map.
|
||||||
...process.env,
|
*
|
||||||
PATH: path,
|
* Next it will attempt to find the desired Go version by checking the
|
||||||
GOPATH: goPath,
|
* following locations:
|
||||||
...opts.env,
|
* 1. The "local" project cache directory (e.g. `.vercel/cache/golang`)
|
||||||
};
|
* 2. The "global" cache directory (e.g. `~/.cache/com.vercel.com/golang`)
|
||||||
if (goMod) {
|
* 3. The system PATH
|
||||||
env.GO111MODULE = 'on';
|
*
|
||||||
|
* 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);
|
return new GoWrapper(env, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadGo(workPath: string, modulePath: string) {
|
/**
|
||||||
const dir = getGoDir(workPath);
|
* Download and installs the Go distribution.
|
||||||
const { platform, arch } = process;
|
*
|
||||||
const version = await parseGoVersion(modulePath);
|
* @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`
|
if (!res.ok) {
|
||||||
const { failed, stdout } = await execa('go', ['version'], { reject: false });
|
throw new Error(`Failed to download: ${url} (${res.status})`);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check `go` bin in cacheDir
|
debug(`Installing go ${version} to ${dest}`);
|
||||||
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);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
await remove(dest);
|
||||||
throw new Error(`Failed to download: ${url} (${res.status})`);
|
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);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
// 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 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> {
|
const goVersionRegExp = /(\d+)\.(\d+)(?:\.(\d+))?/;
|
||||||
// default to newest (first)
|
|
||||||
let version = Array.from(versionMap.values())[0];
|
/**
|
||||||
|
* 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');
|
const file = join(modulePath, 'go.mod');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = await readFile(file, 'utf8');
|
const content = await readFile(file, 'utf8');
|
||||||
const matches = /^go (\d+)\.(\d+)\.?$/gm.exec(content) || [];
|
const matches = /^go (\d+)\.(\d+)\.?$/gm.exec(content) || [];
|
||||||
@@ -189,7 +361,7 @@ async function parseGoVersion(modulePath: string): Promise<string> {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`Warning: Unknown Go version in ${file}`);
|
console.log(`Warning: Unknown Go version in ${file}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
debug(`File not found: ${file}`);
|
debug(`File not found: ${file}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -197,6 +369,5 @@ async function parseGoVersion(modulePath: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(`Selected Go version ${version}`);
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import retry from 'async-retry';
|
import retry from 'async-retry';
|
||||||
import { homedir, tmpdir } from 'os';
|
import { homedir, tmpdir } from 'os';
|
||||||
import { execFileSync, spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import once from '@tootallnate/once';
|
import once from '@tootallnate/once';
|
||||||
import { join, dirname, basename, normalize, sep } from 'path';
|
import { join, dirname, basename, normalize, posix, sep } from 'path';
|
||||||
import {
|
import {
|
||||||
readFile,
|
readFile,
|
||||||
writeFile,
|
writeFile,
|
||||||
|
lstat,
|
||||||
pathExists,
|
pathExists,
|
||||||
mkdirp,
|
mkdirp,
|
||||||
move,
|
move,
|
||||||
|
readlink,
|
||||||
remove,
|
remove,
|
||||||
rmdir,
|
rmdir,
|
||||||
readdir,
|
readdir,
|
||||||
|
unlink,
|
||||||
} from 'fs-extra';
|
} from 'fs-extra';
|
||||||
import {
|
import {
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
@@ -33,19 +36,20 @@ import {
|
|||||||
const TMP = tmpdir();
|
const TMP = tmpdir();
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
cacheDir,
|
||||||
createGo,
|
createGo,
|
||||||
getAnalyzedEntrypoint,
|
getAnalyzedEntrypoint,
|
||||||
cacheDir,
|
|
||||||
OUT_EXTENSION,
|
OUT_EXTENSION,
|
||||||
} from './go-helpers';
|
} from './go-helpers';
|
||||||
|
|
||||||
const handlerFileName = `handler${OUT_EXTENSION}`;
|
const handlerFileName = `handler${OUT_EXTENSION}`;
|
||||||
|
|
||||||
export { shouldServe };
|
export { shouldServe };
|
||||||
|
|
||||||
interface Analyzed {
|
interface Analyzed {
|
||||||
found?: boolean;
|
|
||||||
packageName: string;
|
|
||||||
functionName: string;
|
functionName: string;
|
||||||
|
packageName: string;
|
||||||
|
watch?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortInfo {
|
interface PortInfo {
|
||||||
@@ -147,7 +151,7 @@ export async function build({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const entrypointAbsolute = join(workPath, entrypoint);
|
const entrypointAbsolute = join(workPath, entrypoint);
|
||||||
const entrypointArr = entrypoint.split(sep);
|
const entrypointArr = entrypoint.split(posix.sep);
|
||||||
|
|
||||||
debug(`Parsing AST for "${entrypoint}"`);
|
debug(`Parsing AST for "${entrypoint}"`);
|
||||||
let analyzed: string;
|
let analyzed: string;
|
||||||
@@ -163,7 +167,7 @@ export async function build({
|
|||||||
dirname(goModAbsPath)
|
dirname(goModAbsPath)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Failed to parse AST for "${entrypoint}"`);
|
console.error(`Failed to parse AST for "${entrypoint}"`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +177,7 @@ export async function build({
|
|||||||
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
console.log(err.message);
|
console.error(err.message);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,18 +293,19 @@ export async function build({
|
|||||||
// we need our `main.go` to be called something else
|
// we need our `main.go` to be called something else
|
||||||
const mainGoFileName = 'main__vc__go__.go';
|
const mainGoFileName = 'main__vc__go__.go';
|
||||||
|
|
||||||
if (packageName !== 'main') {
|
const go = await createGo({
|
||||||
const go = await createGo(
|
modulePath: goModPath,
|
||||||
workPath,
|
opts: {
|
||||||
goPath,
|
cwd: entrypointDirname,
|
||||||
process.platform,
|
env: {
|
||||||
process.arch,
|
GOARCH: 'amd64',
|
||||||
{
|
GOOS: 'linux',
|
||||||
cwd: entrypointDirname,
|
|
||||||
stdio: 'inherit',
|
|
||||||
},
|
},
|
||||||
true
|
},
|
||||||
);
|
workPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (packageName !== 'main') {
|
||||||
if (!isGoModExist) {
|
if (!isGoModExist) {
|
||||||
try {
|
try {
|
||||||
const defaultGoModContent = `module ${packageName}`;
|
const defaultGoModContent = `module ${packageName}`;
|
||||||
@@ -321,7 +326,7 @@ export async function build({
|
|||||||
from: join(entrypointDirname, 'go.sum'),
|
from: join(entrypointDirname, 'go.sum'),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Failed to create default go.mod for ${packageName}`);
|
console.error(`Failed to create default go.mod for ${packageName}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +358,7 @@ export async function build({
|
|||||||
if (isGoModExist && isGoModInRootDir) {
|
if (isGoModExist && isGoModInRootDir) {
|
||||||
debug('[mod-root] Write main file to ' + downloadPath);
|
debug('[mod-root] Write main file to ' + downloadPath);
|
||||||
await writeFile(join(downloadPath, mainGoFileName), mainModGoContents);
|
await writeFile(join(downloadPath, mainGoFileName), mainModGoContents);
|
||||||
|
|
||||||
undoFileActions.push({
|
undoFileActions.push({
|
||||||
to: undefined, // delete
|
to: undefined, // delete
|
||||||
from: join(downloadPath, mainGoFileName),
|
from: join(downloadPath, mainGoFileName),
|
||||||
@@ -403,7 +409,7 @@ export async function build({
|
|||||||
undoDirectoryCreation.push(dirname(finalDestination));
|
undoDirectoryCreation.push(dirname(finalDestination));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Failed to move entry to package folder');
|
console.error('Failed to move entry to package folder');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +427,7 @@ export async function build({
|
|||||||
// ensure go.mod up-to-date
|
// ensure go.mod up-to-date
|
||||||
await go.mod();
|
await go.mod();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('failed to `go mod tidy`');
|
console.error('failed to `go mod tidy`');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,23 +439,13 @@ export async function build({
|
|||||||
|
|
||||||
await go.build(src, destPath);
|
await go.build(src, destPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('failed to `go build`');
|
console.error('failed to `go build`');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// legacy mode
|
// legacy mode
|
||||||
// we need `main.go` in the same dir as the entrypoint,
|
// we need `main.go` in the same dir as the entrypoint,
|
||||||
// otherwise `go build` will refuse to build
|
// otherwise `go build` will refuse to build
|
||||||
const go = await createGo(
|
|
||||||
workPath,
|
|
||||||
goPath,
|
|
||||||
process.platform,
|
|
||||||
process.arch,
|
|
||||||
{
|
|
||||||
cwd: entrypointDirname,
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
const originalMainGoContents = await readFile(
|
const originalMainGoContents = await readFile(
|
||||||
join(__dirname, 'main.go'),
|
join(__dirname, 'main.go'),
|
||||||
'utf8'
|
'utf8'
|
||||||
@@ -472,7 +468,7 @@ export async function build({
|
|||||||
try {
|
try {
|
||||||
await go.get();
|
await go.get();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Failed to `go get`');
|
console.error('Failed to `go get`');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +481,7 @@ export async function build({
|
|||||||
].map(file => normalize(file));
|
].map(file => normalize(file));
|
||||||
await go.build(src, destPath);
|
await go.build(src, destPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('failed to `go build`');
|
console.error('failed to `go build`');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,7 +509,9 @@ export async function build({
|
|||||||
undoFunctionRenames
|
undoFunctionRenames
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Build cleanup failed: ${error.message}`);
|
if (error instanceof Error) {
|
||||||
|
console.error(`Build cleanup failed: ${error.message}`);
|
||||||
|
}
|
||||||
debug('Cleanup Error: ' + error);
|
debug('Cleanup Error: ' + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -640,6 +638,19 @@ async function copyDevServer(
|
|||||||
await writeFile(join(dest, 'vercel-dev-server-main.go'), patched);
|
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(
|
export async function startDevServer(
|
||||||
opts: StartDevServerOptions
|
opts: StartDevServerOptions
|
||||||
): Promise<StartDevServerResult> {
|
): Promise<StartDevServerResult> {
|
||||||
@@ -678,6 +689,7 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
copyEntrypoint(entrypointWithExt, tmpPackage),
|
copyEntrypoint(entrypointWithExt, tmpPackage),
|
||||||
copyDevServer(analyzed.functionName, tmpPackage),
|
copyDevServer(analyzed.functionName, tmpPackage),
|
||||||
|
goModAbsPathDir ? null : writeDefaultGoMod(tmp, analyzed.packageName),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const portFile = join(
|
const portFile = join(
|
||||||
@@ -693,13 +705,22 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
|
|||||||
process.platform === 'win32' ? '.exe' : ''
|
process.platform === 'win32' ? '.exe' : ''
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
debug(`SPAWNING go build -o ${executable} ./... CWD=${tmp}`);
|
// Note: We must run `go build`, then manually spawn the dev server instead
|
||||||
execFileSync('go', ['build', '-o', executable, './...'], {
|
// of spawning `go run`. See https://github.com/vercel/vercel/pull/8718 for
|
||||||
cwd: tmp,
|
// more info.
|
||||||
env,
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// 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}`);
|
debug(`SPAWNING ${executable} CWD=${tmp}`);
|
||||||
const child = spawn(executable, [], {
|
const child = spawn(executable, [], {
|
||||||
cwd: tmp,
|
cwd: tmp,
|
||||||
@@ -770,10 +791,10 @@ async function waitForPortFile_(opts: {
|
|||||||
try {
|
try {
|
||||||
const port = Number(await readFile(opts.portFile, 'ascii'));
|
const port = Number(await readFile(opts.portFile, 'ascii'));
|
||||||
retry(() => remove(opts.portFile)).catch((err: Error) => {
|
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 };
|
return { port };
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
if (err.code !== 'ENOENT') {
|
if (err.code !== 'ENOENT') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -784,6 +805,25 @@ async function waitForPortFile_(opts: {
|
|||||||
export async function prepareCache({
|
export async function prepareCache({
|
||||||
workPath,
|
workPath,
|
||||||
}: PrepareCacheOptions): Promise<Files> {
|
}: 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);
|
const cache = await glob(`${cacheDir}/**`, workPath);
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"@types/node": "14.18.33",
|
"@types/node": "14.18.33",
|
||||||
"@types/node-fetch": "^2.3.0",
|
"@types/node-fetch": "^2.3.0",
|
||||||
"@types/tar": "^4.0.0",
|
"@types/tar": "^4.0.0",
|
||||||
|
"@types/yauzl-promise": "2.1.0",
|
||||||
"@vercel/build-utils": "6.3.4",
|
"@vercel/build-utils": "6.3.4",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"async-retry": "1.3.1",
|
"async-retry": "1.3.1",
|
||||||
@@ -44,6 +45,8 @@
|
|||||||
"node-fetch": "^2.2.1",
|
"node-fetch": "^2.2.1",
|
||||||
"string-argv": "0.3.1",
|
"string-argv": "0.3.1",
|
||||||
"tar": "4.4.6",
|
"tar": "4.4.6",
|
||||||
"typescript": "4.3.4"
|
"typescript": "4.3.4",
|
||||||
|
"xdg-app-paths": "5.1.0",
|
||||||
|
"yauzl-promise": "2.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
|
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
|
||||||
],
|
],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/", "mustContain": "cow:go1.19.5:RANDOMNESS_PLACEHOLDER" },
|
{ "path": "/", "mustContain": "cow:go1.19.6:RANDOMNESS_PLACEHOLDER" },
|
||||||
{
|
{
|
||||||
"path": "/subdirectory",
|
"path": "/subdirectory",
|
||||||
"mustContain": "subcow:go1.19.5:RANDOMNESS_PLACEHOLDER"
|
"mustContain": "subcow:go1.19.6:RANDOMNESS_PLACEHOLDER"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/go/test/fixtures/10-go-mod-newer/go.mod
vendored
Normal file
3
packages/go/test/fixtures/10-go-mod-newer/go.mod
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module go-mod
|
||||||
|
|
||||||
|
go 1.19
|
||||||
12
packages/go/test/fixtures/10-go-mod-newer/index.go
vendored
Normal file
12
packages/go/test/fixtures/10-go-mod-newer/index.go
vendored
Normal 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())
|
||||||
|
}
|
||||||
7
packages/go/test/fixtures/10-go-mod-newer/vercel.json
vendored
Normal file
7
packages/go/test/fixtures/10-go-mod-newer/vercel.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "index.go", "use": "@vercel/go" }],
|
||||||
|
"probes": [
|
||||||
|
{ "path": "/", "mustContain": "version:go1.19.6:RANDOMNESS_PLACEHOLDER" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [{ "src": "index.go", "use": "@vercel/go" }],
|
"builds": [{ "src": "index.go", "use": "@vercel/go" }],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/", "mustContain": "version:go1.15.8:RANDOMNESS_PLACEHOLDER" }
|
{ "path": "/", "mustContain": "version:go1.15.15:RANDOMNESS_PLACEHOLDER" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -661,6 +661,7 @@ importers:
|
|||||||
'@types/node': 14.18.33
|
'@types/node': 14.18.33
|
||||||
'@types/node-fetch': ^2.3.0
|
'@types/node-fetch': ^2.3.0
|
||||||
'@types/tar': ^4.0.0
|
'@types/tar': ^4.0.0
|
||||||
|
'@types/yauzl-promise': 2.1.0
|
||||||
'@vercel/build-utils': 6.3.4
|
'@vercel/build-utils': 6.3.4
|
||||||
'@vercel/ncc': 0.24.0
|
'@vercel/ncc': 0.24.0
|
||||||
async-retry: 1.3.1
|
async-retry: 1.3.1
|
||||||
@@ -670,6 +671,8 @@ importers:
|
|||||||
string-argv: 0.3.1
|
string-argv: 0.3.1
|
||||||
tar: 4.4.6
|
tar: 4.4.6
|
||||||
typescript: 4.3.4
|
typescript: 4.3.4
|
||||||
|
xdg-app-paths: 5.1.0
|
||||||
|
yauzl-promise: 2.1.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tootallnate/once': 1.1.2
|
'@tootallnate/once': 1.1.2
|
||||||
'@types/async-retry': 1.4.2
|
'@types/async-retry': 1.4.2
|
||||||
@@ -679,6 +682,7 @@ importers:
|
|||||||
'@types/node': 14.18.33
|
'@types/node': 14.18.33
|
||||||
'@types/node-fetch': 2.6.2
|
'@types/node-fetch': 2.6.2
|
||||||
'@types/tar': 4.0.5
|
'@types/tar': 4.0.5
|
||||||
|
'@types/yauzl-promise': 2.1.0
|
||||||
'@vercel/build-utils': link:../build-utils
|
'@vercel/build-utils': link:../build-utils
|
||||||
'@vercel/ncc': 0.24.0
|
'@vercel/ncc': 0.24.0
|
||||||
async-retry: 1.3.1
|
async-retry: 1.3.1
|
||||||
@@ -688,6 +692,8 @@ importers:
|
|||||||
string-argv: 0.3.1
|
string-argv: 0.3.1
|
||||||
tar: 4.4.6
|
tar: 4.4.6
|
||||||
typescript: 4.3.4
|
typescript: 4.3.4
|
||||||
|
xdg-app-paths: 5.1.0
|
||||||
|
yauzl-promise: 2.1.3
|
||||||
|
|
||||||
packages/hydrogen:
|
packages/hydrogen:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
|||||||
Reference in New Issue
Block a user