mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 04:22:01 +00:00
This reverts commit 8d1afc026f.
This commit is contained in:
@@ -19,6 +19,11 @@ indent_style = space
|
|||||||
[*.py]
|
[*.py]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
[*.asm]
|
[*.asm]
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
|
|
||||||
|
|||||||
1
packages/now-cli/test/dev/fixtures/go/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/go/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.vercel
|
||||||
10
packages/now-cli/test/dev/fixtures/go/api/[segment].go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/[segment].go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||||
|
}
|
||||||
10
packages/now-cli/test/dev/fixtures/go/api/another.go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/another.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package another
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Another(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "This is another page")
|
||||||
|
}
|
||||||
10
packages/now-cli/test/dev/fixtures/go/api/index.go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/index.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "This is the index page")
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import ms from 'ms';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
|
import { isIP } from 'net';
|
||||||
import { join, resolve, delimiter } from 'path';
|
import { join, resolve, delimiter } from 'path';
|
||||||
import _execa from 'execa';
|
import _execa from 'execa';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
@@ -1600,3 +1601,48 @@ test(
|
|||||||
await testPath(200, '/index.css', 'This is index.css');
|
await testPath(200, '/index.css', 'This is index.css');
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'[vercel dev] Should support `*.go` API serverless functions',
|
||||||
|
testFixtureStdio('go', async testPath => {
|
||||||
|
await testPath(200, `/api`, 'This is the index page');
|
||||||
|
await testPath(200, `/api/index`, 'This is the index page');
|
||||||
|
await testPath(200, `/api/index.go`, 'This is the index page');
|
||||||
|
await testPath(200, `/api/another`, 'This is another page');
|
||||||
|
await testPath(200, '/api/another.go', 'This is another page');
|
||||||
|
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||||
|
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
|
||||||
|
testFixtureStdio('node-ts-node-target', async testPath => {
|
||||||
|
await testPath(200, `/api/subclass`, '{"ok":true}');
|
||||||
|
await testPath(
|
||||||
|
200,
|
||||||
|
`/api/array`,
|
||||||
|
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
|
||||||
|
);
|
||||||
|
|
||||||
|
await testPath(200, `/api/dump`, (t, body, res, isDev) => {
|
||||||
|
const { host } = new URL(res.url);
|
||||||
|
const { env, headers } = JSON.parse(body);
|
||||||
|
|
||||||
|
// Test that the API endpoint receives the Vercel proxy request headers
|
||||||
|
t.is(headers['x-forwarded-host'], host);
|
||||||
|
t.is(headers['x-vercel-deployment-url'], host);
|
||||||
|
t.truthy(isIP(headers['x-real-ip']));
|
||||||
|
t.truthy(isIP(headers['x-forwarded-for']));
|
||||||
|
t.truthy(isIP(headers['x-vercel-forwarded-for']));
|
||||||
|
|
||||||
|
// Test that the API endpoint has the Vercel platform env vars defined.
|
||||||
|
t.regex(env.NOW_REGION, /^[a-z]{3}\d$/);
|
||||||
|
if (isDev) {
|
||||||
|
// Only dev is tested because in production these are opt-in.
|
||||||
|
t.is(env.VERCEL_URL, host);
|
||||||
|
t.is(env.VERCEL_REGION, 'dev1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
rm -rf dist
|
||||||
|
|
||||||
|
# Build with `ncc`
|
||||||
ncc build index.ts -e @vercel/build-utils -e @now/build-utils -o dist
|
ncc build index.ts -e @vercel/build-utils -e @now/build-utils -o dist
|
||||||
ncc build install.ts -e @vercel/build-utils -e @now/build-utils -o dist/install
|
ncc build install.ts -e @vercel/build-utils -e @now/build-utils -o dist/install
|
||||||
|
|
||||||
|
# Move `install.js` to dist
|
||||||
mv dist/install/index.js dist/install.js
|
mv dist/install/index.js dist/install.js
|
||||||
rm -rf dist/install
|
rm -rf dist/install
|
||||||
|
|||||||
@@ -3,14 +3,10 @@ import execa from 'execa';
|
|||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { mkdirp, pathExists } from 'fs-extra';
|
import { mkdirp, pathExists } from 'fs-extra';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { homedir } from 'os';
|
|
||||||
import buildUtils from './build-utils';
|
import buildUtils from './build-utils';
|
||||||
import stringArgv from 'string-argv';
|
import stringArgv from 'string-argv';
|
||||||
const { debug } = buildUtils;
|
const { debug } = buildUtils;
|
||||||
const archMap = new Map([
|
const archMap = new Map([['x64', 'amd64'], ['x86', '386']]);
|
||||||
['x64', 'amd64'],
|
|
||||||
['x86', '386'],
|
|
||||||
]);
|
|
||||||
const platformMap = new Map([['win32', 'windows']]);
|
const platformMap = new Map([['win32', 'windows']]);
|
||||||
|
|
||||||
// Location where the `go` binary will be installed after `postinstall`
|
// Location where the `go` binary will be installed after `postinstall`
|
||||||
@@ -130,50 +126,35 @@ export async function downloadGo(
|
|||||||
platform = process.platform,
|
platform = process.platform,
|
||||||
arch = process.arch
|
arch = process.arch
|
||||||
) {
|
) {
|
||||||
// Check default `Go` in user machine
|
// Check if `go` is already installed in user's `$PATH`
|
||||||
const isUserGo = await pathExists(join(homedir(), 'go'));
|
const { failed, stdout } = await execa('go', ['version'], { reject: false });
|
||||||
|
|
||||||
// If we found GOPATH in ENV, or default `Go` path exists
|
if (!failed && parseInt(stdout.split('.')[1]) >= 11) {
|
||||||
// asssume that user have `Go` installed
|
debug('Using system installed version of `go`: %o', stdout.trim());
|
||||||
if (isUserGo || process.env.GOPATH !== undefined) {
|
|
||||||
const { stdout } = await execa('go', ['version']);
|
|
||||||
|
|
||||||
if (parseInt(stdout.split('.')[1]) >= 11) {
|
|
||||||
return createGo(dir, platform, arch);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Your current ${stdout} doesn't support Go Modules. Please update.`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Check `Go` bin in builder CWD
|
|
||||||
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) {
|
|
||||||
throw new Error(`Failed to download: ${url} (${res.status})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(dir, platform, arch);
|
return createGo(dir, platform, arch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check `go` bin in builder CWD
|
||||||
|
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) {
|
||||||
|
throw new Error(`Failed to download: ${url} (${res.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(dir, platform, arch);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
import { join, sep, dirname, basename, normalize } from 'path';
|
|
||||||
import { readFile, writeFile, pathExists, move } from 'fs-extra';
|
|
||||||
import { homedir } from 'os';
|
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import { BuildOptions, Meta, Files, shouldServe } from '@vercel/build-utils';
|
import retry from 'async-retry';
|
||||||
|
import { homedir, tmpdir } from 'os';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
import once from '@tootallnate/once';
|
||||||
|
import { join, dirname, basename, normalize, sep } from 'path';
|
||||||
|
import {
|
||||||
|
readFile,
|
||||||
|
writeFile,
|
||||||
|
pathExists,
|
||||||
|
mkdirp,
|
||||||
|
move,
|
||||||
|
remove,
|
||||||
|
} from 'fs-extra';
|
||||||
|
import {
|
||||||
|
BuildOptions,
|
||||||
|
Meta,
|
||||||
|
Files,
|
||||||
|
StartDevServerOptions,
|
||||||
|
StartDevServerResult,
|
||||||
|
} from '@vercel/build-utils';
|
||||||
import buildUtils from './build-utils';
|
import buildUtils from './build-utils';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -10,12 +27,17 @@ const {
|
|||||||
download,
|
download,
|
||||||
createLambda,
|
createLambda,
|
||||||
getWriteableDirectory,
|
getWriteableDirectory,
|
||||||
|
shouldServe,
|
||||||
debug,
|
debug,
|
||||||
} = buildUtils;
|
} = buildUtils;
|
||||||
|
|
||||||
|
const TMP = tmpdir();
|
||||||
|
|
||||||
import { createGo, getAnalyzedEntrypoint, OUT_EXTENSION } from './go-helpers';
|
import { createGo, getAnalyzedEntrypoint, OUT_EXTENSION } from './go-helpers';
|
||||||
const handlerFileName = `handler${OUT_EXTENSION}`;
|
const handlerFileName = `handler${OUT_EXTENSION}`;
|
||||||
|
|
||||||
|
export { shouldServe };
|
||||||
|
|
||||||
interface Analyzed {
|
interface Analyzed {
|
||||||
found?: boolean;
|
found?: boolean;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
@@ -23,16 +45,22 @@ interface Analyzed {
|
|||||||
watch: string[];
|
watch: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PortInfo {
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize private git repo for Go Modules
|
// Initialize private git repo for Go Modules
|
||||||
async function initPrivateGit(credentials: string) {
|
async function initPrivateGit(credentials: string) {
|
||||||
|
const gitCredentialsPath = join(homedir(), '.git-credentials');
|
||||||
|
|
||||||
await execa('git', [
|
await execa('git', [
|
||||||
'config',
|
'config',
|
||||||
'--global',
|
'--global',
|
||||||
'credential.helper',
|
'credential.helper',
|
||||||
`store --file ${join(homedir(), '.git-credentials')}`,
|
`store --file ${gitCredentialsPath}`,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await writeFile(join(homedir(), '.git-credentials'), credentials);
|
await writeFile(gitCredentialsPath, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -435,4 +463,160 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { shouldServe };
|
function isPortInfo(v: any): v is PortInfo {
|
||||||
|
return v && typeof v.port === 'number';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReadable(v: any): v is Readable {
|
||||||
|
return v && v.readable === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyEntrypoint(entrypoint: string, dest: string): Promise<void> {
|
||||||
|
const data = await readFile(entrypoint, 'utf8');
|
||||||
|
|
||||||
|
// Modify package to `package main`
|
||||||
|
const patched = data.replace(/\bpackage\W+\S+\b/, 'package main');
|
||||||
|
|
||||||
|
await writeFile(join(dest, 'entrypoint.go'), patched);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyDevServer(
|
||||||
|
functionName: string,
|
||||||
|
dest: string
|
||||||
|
): Promise<void> {
|
||||||
|
const data = await readFile(join(__dirname, 'dev-server.go'), 'utf8');
|
||||||
|
|
||||||
|
// Populate the handler function name
|
||||||
|
const patched = data.replace('__HANDLER_FUNC_NAME', functionName);
|
||||||
|
|
||||||
|
await writeFile(join(dest, 'vercel-dev-server-main.go'), patched);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startDevServer(
|
||||||
|
opts: StartDevServerOptions
|
||||||
|
): Promise<StartDevServerResult> {
|
||||||
|
const { entrypoint, workPath, meta = {} } = opts;
|
||||||
|
const { devCacheDir = join(workPath, '.vercel', 'cache') } = meta;
|
||||||
|
const entrypointDir = dirname(entrypoint);
|
||||||
|
|
||||||
|
// For some reason, if `entrypoint` is a path segment (filename contains `[]`
|
||||||
|
// brackets) then the `.go` suffix on the entrypoint is missing. Fix that here…
|
||||||
|
let entrypointWithExt = entrypoint;
|
||||||
|
if (!entrypoint.endsWith('.go')) {
|
||||||
|
entrypointWithExt += '.go';
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmp = join(devCacheDir, 'go', Math.random().toString(32).substring(2));
|
||||||
|
const tmpPackage = join(tmp, entrypointDir);
|
||||||
|
await mkdirp(tmpPackage);
|
||||||
|
|
||||||
|
let goModAbsPathDir = '';
|
||||||
|
if (await pathExists(join(workPath, 'go.mod'))) {
|
||||||
|
goModAbsPathDir = workPath;
|
||||||
|
}
|
||||||
|
const analyzedRaw = await getAnalyzedEntrypoint(
|
||||||
|
entrypointWithExt,
|
||||||
|
goModAbsPathDir
|
||||||
|
);
|
||||||
|
if (!analyzedRaw) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find an exported function in "${entrypointWithExt}"
|
||||||
|
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const analyzed: Analyzed = JSON.parse(analyzedRaw);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
copyEntrypoint(entrypointWithExt, tmpPackage),
|
||||||
|
copyDevServer(analyzed.functionName, tmpPackage),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const portFile = join(
|
||||||
|
TMP,
|
||||||
|
`vercel-dev-port-${Math.random().toString(32).substring(2)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const env: typeof process.env = {
|
||||||
|
...process.env,
|
||||||
|
...meta.env,
|
||||||
|
VERCEL_DEV_PORT_FILE: portFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tmpRelative = `.${sep}${entrypointDir}`;
|
||||||
|
const child = spawn('go', ['run', tmpRelative], {
|
||||||
|
cwd: tmp,
|
||||||
|
env,
|
||||||
|
stdio: ['ignore', 'inherit', 'inherit', 'pipe'],
|
||||||
|
});
|
||||||
|
|
||||||
|
child.once('exit', () => {
|
||||||
|
retry(() => remove(tmp)).catch((err: Error) => {
|
||||||
|
console.error('Could not delete tmp directory: %j: %s', tmp, err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const portPipe = child.stdio[3];
|
||||||
|
if (!isReadable(portPipe)) {
|
||||||
|
throw new Error('File descriptor 3 is not readable');
|
||||||
|
}
|
||||||
|
|
||||||
|
// `dev-server.go` writes the ephemeral port number to FD 3 to be consumed here
|
||||||
|
const onPort = new Promise<PortInfo>(resolve => {
|
||||||
|
portPipe.setEncoding('utf8');
|
||||||
|
portPipe.once('data', d => {
|
||||||
|
resolve({ port: Number(d) });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const onPortFile = waitForPortFile(portFile);
|
||||||
|
const onExit = once.spread<[number, string | null]>(child, 'exit');
|
||||||
|
const result = await Promise.race([onPort, onPortFile, onExit]);
|
||||||
|
onExit.cancel();
|
||||||
|
onPortFile.cancel();
|
||||||
|
|
||||||
|
if (isPortInfo(result)) {
|
||||||
|
return {
|
||||||
|
port: result.port,
|
||||||
|
pid: child.pid,
|
||||||
|
};
|
||||||
|
} else if (Array.isArray(result)) {
|
||||||
|
// Got "exit" event from child process
|
||||||
|
throw new Error(
|
||||||
|
`Failed to start dev server for "${entrypointWithExt}" (code=${result[0]}, signal=${result[1]})`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected result type: ${typeof result}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CancelablePromise<T> extends Promise<T> {
|
||||||
|
cancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForPortFile(portFile: string) {
|
||||||
|
const opts = { portFile, canceled: false };
|
||||||
|
const promise = waitForPortFile_(opts) as CancelablePromise<PortInfo | void>;
|
||||||
|
promise.cancel = () => {
|
||||||
|
opts.canceled = true;
|
||||||
|
};
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForPortFile_(opts: {
|
||||||
|
portFile: string;
|
||||||
|
canceled: boolean;
|
||||||
|
}): Promise<PortInfo | void> {
|
||||||
|
while (!opts.canceled) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
return { port };
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
vc "github.com/vercel/go-bridge/go/bridge"
|
vc "github.com/vercel/go-bridge/go/bridge"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"__NOW_HANDLER_PACKAGE_NAME"
|
||||||
"__NOW_HANDLER_PACKAGE_NAME"
|
"net/http"
|
||||||
|
|
||||||
vc "github.com/vercel/go-bridge/go/bridge"
|
vc "github.com/vercel/go-bridge/go/bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
vc.Start(http.HandlerFunc(__NOW_HANDLER_FUNC_NAME))
|
vc.Start(http.HandlerFunc(__NOW_HANDLER_FUNC_NAME))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tootallnate/once": "1.1.2",
|
||||||
|
"@types/async-retry": "1.4.2",
|
||||||
"@types/execa": "^0.9.0",
|
"@types/execa": "^0.9.0",
|
||||||
"@types/fs-extra": "^5.0.5",
|
"@types/fs-extra": "^5.0.5",
|
||||||
"@types/node-fetch": "^2.3.0",
|
"@types/node-fetch": "^2.3.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user