Compare commits

..

17 Commits

Author SHA1 Message Date
Matthew Stanciu
a0479338c1 , 2022-07-12 12:58:44 -07:00
Matthew Stanciu
37a8037128 Add 2022-07-12 11:55:25 -07:00
Matthew Stanciu
65fd5c49f9 Optionally pass in state to mock deployment 2022-07-08 16:36:54 -07:00
Matthew Stanciu
75a19a1b8f Remove console.logs 2022-07-08 16:20:09 -07:00
Matthew Stanciu
d2456a21a1 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 13:03:39 -07:00
Matthew Stanciu
8f02a5d0b1 Fix unit tests 2022-07-08 12:03:14 -07:00
Matthew Stanciu
f4cb7b3b8b Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 11:57:56 -07:00
Matthew Stanciu
88c309ec40 use title module for state string 2022-07-08 10:46:42 -07:00
Matthew Stanciu
e383068427 styfle suggestions 2022-07-08 10:39:14 -07:00
Sean Massa
d660f110a3 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 11:39:39 -05:00
Matthew Stanciu
9fada88f99 Fix unit tests 2022-07-07 21:31:03 -07:00
Matthew Stanciu
0d9082dcb8 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-07 21:13:32 -07:00
Matthew Stanciu
4da688cff3 Merge branch 'update/fix-ls-visual-bugs' of https://github.com/vercel/vercel into update/fix-ls-visual-bugs 2022-07-07 17:04:07 -07:00
Matthew Stanciu
af40cf43ae Fix tests 2022-07-07 17:03:41 -07:00
Matthew Stanciu
4c100a191c Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-07 16:43:15 -07:00
Matthew Stanciu
a637f2f949 Remove project ls message 2022-07-07 16:43:02 -07:00
Matthew Stanciu
f3683fff3f Fix ls visual bugs 2022-07-07 16:37:14 -07:00
190 changed files with 1452 additions and 3865 deletions

View File

@@ -1,5 +1,4 @@
# https://prettier.io/docs/en/ignore.html
# ignore these files with an intentional syntax error
# ignore this file with an intentional syntax error
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@sentry/node": "5.11.1",
"got": "10.2.1",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"parse-github-url": "1.0.2",
"tar-fs": "2.0.0",
"unzip-stream": "0.3.0"

View File

@@ -26,12 +26,12 @@
"jest": "28.0.2",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"prettier": "2.6.2",
"ts-eager": "2.0.2",
"ts-jest": "28.0.5",
"turbo": "1.3.2-canary.1"
"turbo": "1.3.1"
},
"scripts": {
"lerna": "lerna",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.0.4",
"version": "5.0.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -44,7 +44,7 @@
"js-yaml": "3.13.1",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"semver": "6.1.1",
"typescript": "4.3.4",
"yazl": "2.5.1"

View File

@@ -33,11 +33,6 @@ export class EdgeFunction {
*/
envVarsInUse?: string[];
/**
* Extra binary files to be included in the edge function
*/
assets?: { name: string; path: string }[];
constructor(params: Omit<EdgeFunction, 'type'>) {
this.type = 'EdgeFunction';
this.name = params.name;
@@ -45,6 +40,5 @@ export class EdgeFunction {
this.entrypoint = params.entrypoint;
this.files = params.files;
this.envVarsInUse = params.envVarsInUse;
this.assets = params.assets;
}
}

View File

@@ -33,6 +33,9 @@ function getHint(isAuto = false) {
: `Please set "engines": { "node": "${range}" } in your \`package.json\` file to use Node.js ${major}.`;
}
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
export function getLatestNodeVersion() {
return allOptions[0];
}
@@ -72,7 +75,7 @@ export async function getSupportedNodeVersion(
throw new NowBuildError({
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
link: 'http://vercel.link/node-version',
message: `${intro} ${getHint(isAuto)}`,
message: `${intro} ${getHint(isAuto)} ${upstreamProvider}`,
});
}
@@ -83,9 +86,9 @@ export async function getSupportedNodeVersion(
console.warn(
`Error: Node.js version ${
selection.range
} has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
} is deprecated. Deployments created on or after ${d} will fail to build. ${getHint(
isAuto
)}`
)} ${upstreamProvider}`
);
}

View File

@@ -1,7 +1,6 @@
import ms from 'ms';
import path from 'path';
import fs, { readlink } from 'fs-extra';
import retry from 'async-retry';
import { strict as assert, strictEqual } from 'assert';
import { createZip } from '../src/lambda';
import { getSupportedNodeVersion } from '../src/fs/node-version';
@@ -387,10 +386,10 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
12
);
expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
]);
global.Date.now = realDateNow;
@@ -495,43 +494,28 @@ it('should only invoke `runNpmInstall()` once per `package.json` file (serial)',
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
const retryOpts = { maxRetryTime: 1000 };
let run1, run2, run3;
await retry(async () => {
run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
}, retryOpts);
await retry(async () => {
run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
}, retryOpts);
await retry(async () => {
run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
}, retryOpts);
const run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
const run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
const run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
});
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
let results: [boolean, boolean, boolean] | undefined;
await retry(
async () => {
results = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
},
{ maxRetryTime: 3000 }
);
const [run1, run2, run3] = results || [];
const [run1, run2, run3] = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
expect(run1).toEqual(true);
expect(run2).toEqual(false);
expect(run3).toEqual(false);

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "27.2.0",
"version": "27.0.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -42,16 +42,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.0.4",
"@vercel/go": "2.0.8",
"@vercel/hydrogen": "0.0.5",
"@vercel/next": "3.1.8",
"@vercel/node": "2.4.5",
"@vercel/python": "3.1.0",
"@vercel/redwood": "1.0.9",
"@vercel/remix": "1.0.10",
"@vercel/ruby": "1.3.16",
"@vercel/static-build": "1.0.9",
"@vercel/build-utils": "5.0.1",
"@vercel/go": "2.0.5",
"@vercel/hydrogen": "0.0.2",
"@vercel/next": "3.1.4",
"@vercel/node": "2.4.1",
"@vercel/python": "3.0.5",
"@vercel/redwood": "1.0.6",
"@vercel/remix": "1.0.6",
"@vercel/ruby": "1.3.13",
"@vercel/static-build": "1.0.5",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -96,11 +96,11 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.1.3",
"@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.1",
"@vercel/fun": "1.0.4",
"@vercel/client": "12.1.0",
"@vercel/frameworks": "1.1.0",
"@vercel/fs-detectors": "1.0.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2",
"alpha-sort": "2.0.1",
@@ -147,7 +147,7 @@
"minimatch": "3.0.4",
"mri": "1.1.5",
"ms": "2.1.2",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"open": "8.4.0",
"ora": "3.4.0",

View File

@@ -56,11 +56,11 @@ async function main() {
args.push('src/index.ts');
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@vercel/fun`'s runtime files:
// `ncc` has some issues with `@zeit/fun`'s runtime files:
// - Executable bits on the `bootstrap` files appear to be lost:
// https://github.com/vercel/ncc/pull/182
// https://github.com/zeit/ncc/pull/182
// - The `bootstrap.js` asset does not get copied into the output dir:
// https://github.com/vercel/ncc/issues/278
// https://github.com/zeit/ncc/issues/278
//
// Aside from those issues, all the same files from the `runtimes` directory
// should be copied into the output runtimes dir, specifically the `index.js`
@@ -70,7 +70,7 @@ async function main() {
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
const runtimes = join(
dirRoot,
'../../node_modules/@vercel/fun/dist/src/runtimes'
'../../node_modules/@zeit/fun/dist/src/runtimes'
);
await cpy('**/*', join(distRoot, 'runtimes'), {
parents: true,

View File

@@ -25,7 +25,7 @@ import {
MergeRoutesProps,
Route,
} from '@vercel/routing-utils';
import type { VercelConfig } from '@vercel/client';
import { VercelConfig } from '@vercel/client';
import pull from './pull';
import { staticFiles as getFiles } from '../util/get-files';
@@ -36,10 +36,7 @@ import * as cli from '../util/pkg-name';
import cliPkg from '../util/pkg';
import readJSONFile from '../util/read-json-file';
import { CantParseJSONFile } from '../util/errors-ts';
import {
ProjectLinkAndSettings,
readProjectSettings,
} from '../util/projects/project-settings';
import { readProjectSettings } from '../util/projects/project-settings';
import { VERCEL_DIR } from '../util/projects/link';
import confirm from '../util/input/confirm';
import { emoji, prependEmoji } from '../util/emoji';
@@ -49,31 +46,11 @@ import {
PathOverride,
writeBuildResult,
} from '../util/build/write-build-result';
import { importBuilders } from '../util/build/import-builders';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders';
import { toEnumerableError } from '../util/error';
type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder {
error?: any;
require?: string;
requirePath?: string;
apiVersion: number;
}
/**
* Contents of the `builds.json` file.
*/
interface BuildsManifest {
'//': string;
target: string;
argv: string[];
error?: any;
builds?: SerializedBuilder[];
}
const help = () => {
return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)}
@@ -190,6 +167,7 @@ export default async function main(client: Client): Promise<number> {
}
// TODO: load env vars from the API, fall back to local files if that fails
const envPath = await checkExists([
join(cwd, VERCEL_DIR, `.env.${target}.local`),
join(cwd, `.env`),
@@ -203,48 +181,6 @@ export default async function main(client: Client): Promise<number> {
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildsJson: BuildsManifest = {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
};
try {
return await doBuild(client, project, buildsJson, cwd, outputDir);
} catch (err: any) {
output.prettyError(err);
// Write error to `builds.json` file
buildsJson.error = toEnumerableError(err);
const buildsJsonPath = join(outputDir, 'builds.json');
const configJsonPath = join(outputDir, 'config.json');
await fs.outputJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
await fs.writeJSON(configJsonPath, { version: 3 }, { spaces: 2 });
return 1;
}
}
/**
* Execute the Project's builders. If this function throws an error,
* then it will be serialized into the `builds.json` manifest file.
*/
async function doBuild(
client: Client,
project: ProjectLinkAndSettings,
buildsJson: BuildsManifest,
cwd: string,
outputDir: string
): Promise<number> {
const { output } = client;
const workPath = join(cwd, project.settings.rootDirectory || '.');
// Load `package.json` and `vercel.json` files
@@ -262,18 +198,19 @@ async function doBuild(
normalizePath(relative(workPath, f))
);
const routesResult = getTransformedRoutes(vercelConfig || {});
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
if (routesResult.error) {
throw routesResult.error;
output.prettyError(routesResult.error);
return 1;
}
if (vercelConfig?.builds && vercelConfig.functions) {
throw new NowBuildError({
code: 'bad_request',
output.prettyError({
message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
link: 'https://vercel.link/functions-and-builds',
});
return 1;
}
let builds = vercelConfig?.builds || [];
@@ -291,12 +228,12 @@ async function doBuild(
const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig,
projectSettings: project.settings,
ignoreBuildScript: true,
featHandleMiss: true,
});
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
throw detectedBuilders.errors[0];
output.prettyError(detectedBuilders.errors[0]);
return 1;
}
for (const w of detectedBuilders.warnings) {
@@ -329,7 +266,13 @@ async function doBuild(
const builderSpecs = new Set(builds.map(b => b.use));
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
let buildersWithPkgs: Map<string, BuilderWithPkg>;
try {
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
} catch (err: any) {
output.prettyError(err);
return 1;
}
// Populate Files -> FileFsRef mapping
const filesMap: Files = {};
@@ -339,6 +282,12 @@ async function doBuild(
filesMap[path] = new FileFsRef({ mode, fsPath });
}
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp();
// Create fresh new output directory
@@ -347,31 +296,32 @@ async function doBuild(
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
ops.push(
fs.writeJSON(
join(outputDir, 'builds.json'),
{
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
builds: builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
return {
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
};
}),
},
{
spaces: 2,
}
const { builder, pkg: builderPkg } = builderWithPkg;
return [
build,
{
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
},
];
})
)
);
buildsJson.builds = Array.from(buildsJsonBuilds.values());
const buildsJsonPath = join(outputDir, 'builds.json');
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
ops.push(writeBuildsJsonPromise);
// The `meta` config property is re-used for each Builder
// invocation so that Builders can share state between
@@ -382,87 +332,65 @@ async function doBuild(
};
// Execute Builders for detected entrypoints
// TODO: parallelize builds (except for frontend)
const sortedBuilders = sortBuilders(builds);
// TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = [];
const repoRootPath = cwd;
const corepackShimDir = await initCorepack({ repoRootPath });
const rootPackageJsonPath = repoRootPath || workPath;
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
for (const build of sortedBuilders) {
for (const build of builds) {
if (typeof build.src !== 'string') continue;
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
try {
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
const buildConfig: Config = {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
builderPkg,
vercelConfig?.cleanUrls
).then(
override => {
if (override) overrides.push(override);
},
err => err
)
);
} catch (err: any) {
const writeConfigJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
{ version: 3 },
{ spaces: 2 }
);
await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
const buildJsonBuild = buildsJsonBuilds.get(build);
if (buildJsonBuild) {
buildJsonBuild.error = toEnumerableError(err);
await fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
}
return 1;
}
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
builderPkg,
vercelConfig?.cleanUrls
).then(
override => {
if (override) overrides.push(override);
},
err => err
)
);
}
if (corepackShimDir) {
@@ -471,12 +399,15 @@ async function doBuild(
// Wait for filesystem operations to complete
// TODO render progress bar?
let hadError = false;
const errors = await Promise.all(ops);
for (const error of errors) {
if (error) {
throw error;
hadError = true;
output.prettyError(error);
}
}
if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced
const configPath = join(outputDir, 'config.json');

View File

@@ -15,7 +15,6 @@ export const help = () => `
)}
dev Start a local development server
env Manages the Environment Variables for your current Project
git Manage Git provider repository for your current Project
init [example] Initialize an example project
ls | list [app] Lists deployments
inspect [id] Displays information related to a deployment

View File

@@ -64,7 +64,7 @@ import { help } from './args';
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
import parseTarget from '../../util/deploy/parse-target';
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
import { createGitMeta } from '../../util/create-git-meta';
import { createGitMeta } from '../../util/deploy/create-git-meta';
export default async (client: Client) => {
const { output } = client;
@@ -95,7 +95,6 @@ export default async (client: Client) => {
// deprecated
'--name': String,
'-n': '--name',
'--no-clipboard': Boolean,
'--target': String,
});
} catch (error) {
@@ -184,17 +183,6 @@ export default async (client: Client) => {
);
}
if (argv['--no-clipboard']) {
output.print(
`${prependEmoji(
`The ${param(
'--no-clipboard'
)} option was ignored because it is the default behavior. Please remove it.`,
emoji('warning')
)}\n`
);
}
// build `target`
const target = parseTarget(output, argv['--target'], argv['--prod']);
if (typeof target === 'number') {
@@ -428,7 +416,7 @@ export default async (client: Client) => {
parseMeta(argv['--meta'])
);
const gitMetadata = await createGitMeta(path, output, project);
const gitMetadata = await createGitMeta(path, output);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign(

View File

@@ -1,9 +1,7 @@
import chalk from 'chalk';
import { ProjectEnvTarget } from '../../types';
import Client from '../../util/client';
import {
getEnvTargetPlaceholder,
isValidEnvTarget,
} from '../../util/env/env-target';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import getSubcommand from '../../util/get-subcommand';
@@ -31,7 +29,6 @@ const help = () => {
${chalk.dim('Options:')}
-h, --help Output usage information
--environment Set the Environment (development, preview, production) when pulling Environment Variables
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
@@ -114,7 +111,6 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), {
'--yes': Boolean,
'-y': '--yes',
'--environment': String,
});
} catch (error) {
handleError(error);
@@ -130,17 +126,6 @@ export default async function main(client: Client) {
const subArgs = argv._.slice(1);
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
const { output, config } = client;
const target = argv['--environment']?.toLowerCase() || 'development';
if (!isValidEnvTarget(target)) {
output.error(
`Invalid environment \`${chalk.cyan(
target
)}\`. Valid options: ${getEnvTargetPlaceholder()}`
);
return 1;
}
const link = await getLinkedProject(client, cwd);
if (link.status === 'error') {
return link.exitCode;
@@ -165,7 +150,7 @@ export default async function main(client: Client) {
return pull(
client,
project,
target,
ProjectEnvTarget.Development,
argv,
args,
output,

View File

@@ -1,205 +0,0 @@
import chalk from 'chalk';
import { join } from 'path';
import { Org, Project } from '../../types';
import Client from '../../util/client';
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
import confirm from '../../util/input/confirm';
import list, { ListChoice } from '../../util/input/list';
import { Output } from '../../util/output';
import link from '../../util/output/link';
import { getCommandName } from '../../util/pkg-name';
import {
connectGitProvider,
disconnectGitProvider,
formatProvider,
parseRepoUrl,
} from '../../util/projects/connect-git-provider';
import validatePaths from '../../util/validate-paths';
export default async function connect(
client: Client,
argv: any,
args: string[],
project: Project | undefined,
org: Org | undefined
) {
const { output } = client;
const confirm = Boolean(argv['--confirm']);
if (args.length !== 0) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('project connect')}`
)}`
);
return 2;
}
if (!project || !org) {
output.error(
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel projet by running ${getCommandName(
'link'
)}.`
);
return 1;
}
let paths = [process.cwd()];
const validate = await validatePaths(client, paths);
if (!validate.valid) {
return validate.exitCode;
}
const { path } = validate;
const gitProviderLink = project.link;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
// get project from .git
const gitConfigPath = join(path, '.git/config');
const gitConfig = await parseGitConfig(gitConfigPath, output);
if (!gitConfig) {
output.error(
`No local git repo found. Run ${chalk.cyan(
'`git clone <url>`'
)} to clone a remote Git repository first.`
);
return 1;
}
const remoteUrls = pluckRemoteUrls(gitConfig);
if (!remoteUrls) {
output.error(
`No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan(
'`git remote --help`'
)} for more details.`
);
return 1;
}
let remoteUrl: string;
if (Object.keys(remoteUrls).length > 1) {
output.log(`Found multiple remote URLs.`);
remoteUrl = await selectRemoteUrl(client, remoteUrls);
} else {
// If only one is found, get it — usually "origin"
remoteUrl = Object.values(remoteUrls)[0];
}
if (remoteUrl === '') {
output.log('Aborted.');
return 0;
}
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
const parsedUrl = parseRepoUrl(remoteUrl);
if (!parsedUrl) {
output.error(
`Failed to parse Git repo data from the following remote URL: ${link(
remoteUrl
)}`
);
return 1;
}
const { provider, org: gitOrg, repo } = parsedUrl;
const repoPath = `${gitOrg}/${repo}`;
let connectedRepoPath;
if (!gitProviderLink) {
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (typeof connect === 'number') {
return connect;
}
} else {
const connectedProvider = gitProviderLink.type;
const connectedOrg = gitProviderLink.org;
const connectedRepo = gitProviderLink.repo;
connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
const isSameRepo =
connectedProvider === provider &&
connectedOrg === gitOrg &&
connectedRepo === repo;
if (isSameRepo) {
output.log(
`${chalk.cyan(connectedRepoPath)} is already connected to your project.`
);
return 1;
}
const shouldReplaceRepo = await confirmRepoConnect(
client,
output,
confirm,
connectedRepoPath
);
if (!shouldReplaceRepo) {
return 0;
}
await disconnectGitProvider(client, org, project.id);
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (typeof connect === 'number') {
return connect;
}
}
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
);
return 0;
}
async function confirmRepoConnect(
client: Client,
output: Output,
yes: boolean,
connectedRepoPath: string
) {
let shouldReplaceProject = yes;
if (!shouldReplaceProject) {
shouldReplaceProject = await confirm(
client,
`Looks like you already have a repository connected: ${chalk.cyan(
connectedRepoPath
)}. Do you want to replace it?`,
true
);
if (!shouldReplaceProject) {
output.log(`Aborted. Repo not connected.`);
}
}
return shouldReplaceProject;
}
async function selectRemoteUrl(
client: Client,
remoteUrls: { [key: string]: string }
): Promise<string> {
let choices: ListChoice[] = [];
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
choices.push({
name: `${urlValue} ${chalk.gray(`(${urlKey})`)}`,
value: urlValue,
short: urlKey,
});
}
return await list(client, {
message: 'Which remote do you want to connect?',
choices,
});
}

View File

@@ -1,58 +0,0 @@
import chalk from 'chalk';
import { Org, Project } from '../../types';
import Client from '../../util/client';
import confirm from '../../util/input/confirm';
import { getCommandName } from '../../util/pkg-name';
import { disconnectGitProvider } from '../../util/projects/connect-git-provider';
export default async function disconnect(
client: Client,
args: string[],
project: Project | undefined,
org: Org | undefined
) {
const { output } = client;
if (args.length !== 0) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('project disconnect')}`
)}`
);
return 2;
}
if (!project || !org) {
output.error('An unexpected error occurred.');
return 1;
}
if (project.link) {
const { org: linkOrg, repo } = project.link;
output.print(
`Your Vercel project will no longer create deployments when you push to this repository.\n`
);
const confirmDisconnect = await confirm(
client,
`Are you sure you want to disconnect ${chalk.cyan(
`${linkOrg}/${repo}`
)} from your project?`,
false
);
if (confirmDisconnect) {
await disconnectGitProvider(client, org, project.id);
output.log(`Disconnected ${chalk.cyan(`${linkOrg}/${repo}`)}.`);
} else {
output.log('Aborted.');
}
} else {
output.error(
`No Git repository connected. Run ${getCommandName(
'project connect'
)} to connect one.`
);
return 1;
}
return 0;
}

View File

@@ -1,94 +0,0 @@
import chalk from 'chalk';
import Client from '../../util/client';
import { ensureLink } from '../../util/ensure-link';
import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name';
import validatePaths from '../../util/validate-paths';
import connect from './connect';
import disconnect from './disconnect';
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} git`)} <command>
${chalk.dim('Commands:')}
connect Connect your Git config "origin" remote as a Git provider to your project
disconnect Disconnect the Git provider repository from your project
${chalk.dim('Options:')}
-h, --help Output usage information
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
${chalk.dim('Examples:')}
${chalk.gray('')} Connect a Git provider repository
${chalk.cyan(`$ ${getPkgName()} git connect`)}
${chalk.gray('')} Disconnect the Git provider repository
${chalk.cyan(`$ ${getPkgName()} git disconnect`)}
`);
};
const COMMAND_CONFIG = {
connect: ['connect'],
disconnect: ['disconnect'],
};
export default async function main(client: Client) {
let argv: any;
let subcommand: string | string[];
try {
argv = getArgs(client.argv.slice(2), {
'--confirm': Boolean,
});
} catch (error) {
handleError(error);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
argv._ = argv._.slice(1);
subcommand = argv._[0];
const args = argv._.slice(1);
const confirm = Boolean(argv['--confirm']);
const { output } = client;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
const { path } = pathValidation;
const linkedProject = await ensureLink('git', client, path, confirm);
if (typeof linkedProject === 'number') {
return linkedProject;
}
const { org, project } = linkedProject;
switch (subcommand) {
case 'connect':
return await connect(client, argv, args, project, org);
case 'disconnect':
return await disconnect(client, args, project, org);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));
help();
return 2;
}
}

View File

@@ -14,7 +14,6 @@ export default new Map([
['domain', 'domains'],
['domains', 'domains'],
['env', 'env'],
['git', 'git'],
['help', 'help'],
['init', 'init'],
['inspect', 'inspect'],

View File

@@ -1,10 +1,10 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import title from 'title';
import Now from '../util';
import getArgs from '../util/get-args';
import { handleError } from '../util/error';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed';
import strlen from '../util/strlen';
@@ -42,6 +42,8 @@ const help = () => {
-m, --meta Filter deployments by metadata (e.g.: ${chalk.dim(
'`-m KEY=value`'
)}). Can appear many times.
-i, --inspect Display dashboard inspect URLs
--prod Filter for production URLs
-N, --next Show next page of results
${chalk.dim('Examples:')}
@@ -77,6 +79,9 @@ export default async function main(client: Client) {
'-m': '--meta',
'--next': Number,
'-N': '--next',
'--inspect': Boolean,
'-i': '--inspect',
'--prod': Boolean,
'--confirm': Boolean,
});
} catch (err) {
@@ -99,9 +104,10 @@ export default async function main(client: Client) {
}
const yes = argv['--confirm'] || false;
const inspect = argv['--inspect'] || false;
const prod = argv['--prod'] || false;
const meta = parseMeta(argv['--meta']);
const { includeScheme } = config;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
@@ -139,6 +145,7 @@ export default async function main(client: Client) {
link.project = linkedProject.project;
}
const isTeam = org?.type === 'team';
let { contextName, team } = await getScope(client);
// If user passed in a custom scope, update the current team & context name
@@ -255,37 +262,51 @@ export default async function main(client: Client) {
}
log(
`Deployments under ${chalk.bold(contextName)} ${elapsed(
`${prod ? `Production deployments` : `Deployments`} for ${chalk.bold(
chalk.magenta(app)
)} under ${chalk.bold(chalk.magenta(contextName))} ${elapsed(
Date.now() - start
)}`
);
// information to help the user find other deployments or instances
if (app == null) {
log(
`To list more deployments for a project run ${cmd(
`${getCommandName('ls [project]')}`
)}`
);
}
log(
`To list more deployments for a project, run ${getCommandName(
'ls [project]'
)}.`
);
print('\n');
client.output.print(
`${table(
[
['project', 'latest deployment', 'state', 'age', 'username'].map(
header => chalk.dim(header)
),
(isTeam
? [
'age',
inspect ? 'inspect url' : 'deployment url',
'state',
'duration',
'username',
]
: [
'age',
inspect ? 'inspect url' : 'deployment url',
'state',
'duration',
]
).map(header => chalk.bold(chalk.cyan(header))),
...deployments
.sort(sortRecent())
.map(dep => [
.map((dep, i) => [
[
getProjectName(dep),
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
stateString(dep.state),
chalk.gray(ms(Date.now() - dep.createdAt)),
dep.creator.username,
i === 0
? chalk.bold(`${getDeployUrl(dep, inspect)}`)
: `${getDeployUrl(dep, inspect)}`,
stateString(dep.state),
chalk.gray(getDeploymentDuration(dep)),
isTeam ? chalk.gray(dep.creator.username) : '',
],
])
// flatten since the previous step returns a nested
@@ -298,8 +319,8 @@ export default async function main(client: Client) {
),
],
{
align: ['l', 'l', 'r', 'l', 'l'],
hsep: ' '.repeat(4),
align: isTeam ? ['l', 'l', 'l', 'l', 'l'] : ['l', 'l', 'l', 'l'],
hsep: ' '.repeat(isTeam ? 4 : 5),
stringLength: strlen,
}
).replace(/^/gm, ' ')}\n\n`
@@ -315,27 +336,43 @@ export default async function main(client: Client) {
}
}
function getProjectName(d: Deployment) {
// We group both file and files into a single project
if (d.name === 'file') {
return 'files';
}
function getDeployUrl(
deployment: Deployment,
inspect: boolean | undefined
): string {
return inspect ? deployment.inspectorUrl : 'https://' + deployment.url;
}
return d.name;
export function getDeploymentDuration(dep: Deployment): string {
if (!dep || !dep.ready || !dep.buildingAt) {
return '?';
}
const duration = ms(dep.ready - dep.buildingAt);
if (duration === '0ms') {
return '--';
}
return duration;
}
// renders the state string
export function stateString(s: string) {
const CIRCLE = '● ';
// make `s` title case
const sTitle = title(s);
switch (s) {
case 'INITIALIZING':
return chalk.yellow(s);
case 'BUILDING':
case 'DEPLOYING':
case 'ANALYZING':
return chalk.yellow(CIRCLE) + sTitle;
case 'ERROR':
return chalk.red(s);
return chalk.red(CIRCLE) + sTitle;
case 'READY':
return s;
return chalk.green(CIRCLE) + sTitle;
case 'QUEUED':
return chalk.white(CIRCLE) + sTitle;
case 'CANCELED':
return chalk.gray(sTitle);
default:
return chalk.gray('UNKNOWN');
}

View File

@@ -6,6 +6,7 @@ import getScope from '../../util/get-scope';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name';
import validatePaths from '../../util/validate-paths';
import add from './add';
import list from './list';
import rm from './rm';
@@ -47,6 +48,7 @@ const COMMAND_CONFIG = {
ls: ['ls', 'list'],
add: ['add'],
rm: ['rm', 'remove'],
connect: ['connect'],
};
export default async function main(client: Client) {
@@ -57,6 +59,7 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), {
'--next': Number,
'-N': '--next',
'--yes': Boolean,
});
} catch (error) {
handleError(error);
@@ -73,6 +76,12 @@ export default async function main(client: Client) {
const args = argv._.slice(1);
const { output } = client;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
let contextName = '';
try {

View File

@@ -173,7 +173,7 @@ const main = async () => {
const targetOrSubcommand = argv._[2];
// Currently no beta commands - add here as needed
const betaCommands: string[] = [];
const betaCommands: string[] = [''];
if (betaCommands.includes(targetOrSubcommand)) {
console.log(
`${chalk.grey(
@@ -632,9 +632,6 @@ const main = async () => {
case 'env':
func = require('./commands/env').default;
break;
case 'git':
func = require('./commands/git').default;
break;
case 'init':
func = require('./commands/init').default;
break;
@@ -750,7 +747,9 @@ const main = async () => {
// Otherwise it is an unexpected error and we should show the trace
// and an unexpected error message
output.error(`An unexpected error occurred in ${subcommand}: ${err}`);
output.error(
`An unexpected error occurred in ${subcommand}: ${err.stack}`
);
}
return 1;

View File

@@ -28,7 +28,6 @@ export interface AuthConfig {
export interface GlobalConfig {
_?: string;
currentTeam?: string;
includeScheme?: string;
collectMetrics?: boolean;
api?: string;
@@ -248,34 +247,12 @@ export interface ProjectEnvVariable {
gitBranch?: string;
}
export interface DeployHook {
createdAt: number;
id: string;
name: string;
ref: string;
url: string;
}
export interface ProjectLinkData {
type: string;
repo: string;
repoId: number;
org?: string;
gitCredentialId: string;
productionBranch?: string | null;
sourceless: boolean;
createdAt: number;
updatedAt: number;
deployHooks?: DeployHook[];
}
export interface Project extends ProjectSettings {
id: string;
name: string;
accountId: string;
updatedAt: number;
createdAt: number;
link?: ProjectLinkData;
alias?: ProjectAliasTarget[];
latestDeployments?: Partial<Deployment>[];
}

View File

@@ -6,9 +6,11 @@ import { VERCEL_DIR } from '../projects/link';
import readJSONFile from '../read-json-file';
export async function initCorepack({
repoRootPath,
cwd,
rootPackageJsonPath,
}: {
repoRootPath: string;
cwd: string;
rootPackageJsonPath: string;
}): Promise<string | null> {
if (process.env.ENABLE_EXPERIMENTAL_COREPACK !== '1') {
// Since corepack is experimental, we need to exit early
@@ -16,7 +18,7 @@ export async function initCorepack({
return null;
}
const pkg = await readJSONFile<PackageJson>(
join(repoRootPath, 'package.json')
join(rootPackageJsonPath, 'package.json')
);
if (pkg instanceof CantParseJSONFile) {
console.warn(
@@ -30,13 +32,16 @@ export async function initCorepack({
console.log(
`Detected ENABLE_EXPERIMENTAL_COREPACK=1 and "${pkg.packageManager}" in package.json`
);
const corepackRootDir = join(repoRootPath, VERCEL_DIR, 'cache', 'corepack');
const corepackRootDir = join(cwd, VERCEL_DIR, 'cache', 'corepack');
const corepackHomeDir = join(corepackRootDir, 'home');
const corepackShimDir = join(corepackRootDir, 'shim');
await fs.mkdirp(corepackHomeDir);
await fs.mkdirp(corepackShimDir);
process.env.COREPACK_HOME = corepackHomeDir;
process.env.PATH = `${corepackShimDir}${delimiter}${process.env.PATH}`;
process.env.DEBUG = process.env.DEBUG
? `corepack,${process.env.DEBUG}`
: 'corepack';
const pkgManagerName = pkg.packageManager.split('@')[0];
// We must explicitly call `corepack enable npm` since `corepack enable`
// doesn't work with npm. See https://github.com/nodejs/corepack/pull/24
@@ -67,4 +72,11 @@ export function cleanupCorepack(corepackShimDir: string) {
''
);
}
if (process.env.DEBUG) {
if (process.env.DEBUG === 'corepack') {
delete process.env.DEBUG;
} else {
process.env.DEBUG = process.env.DEBUG.replace('corepack,', '');
}
}
}

View File

@@ -1,12 +0,0 @@
import frameworkList from '@vercel/frameworks';
export function sortBuilders<B extends { use: string }>(builds: B[]): B[] {
const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
);
const toNumber = (build: B) => (frontendRuntimeSet.has(build.use) ? 0 : 1);
return builds.sort((build1, build2) => {
return toNumber(build1) - toNumber(build2);
});
}

View File

@@ -1,14 +1,6 @@
import fs from 'fs-extra';
import mimeTypes from 'mime-types';
import {
basename,
dirname,
extname,
join,
relative,
resolve,
posix,
} from 'path';
import { basename, dirname, extname, join, relative, resolve } from 'path';
import {
Builder,
BuildResultV2,
@@ -28,7 +20,6 @@ import pipe from 'promisepipe';
import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link';
const { normalize } = posix;
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult(
@@ -76,13 +67,6 @@ export interface PathOverride {
path?: string;
}
/**
* Remove duplicate slashes as well as leading/trailing slashes.
*/
function stripDuplicateSlashes(path: string): string {
return normalize(path).replace(/(^\/|\/$)/g, '');
}
/**
* Writes the output from the `build()` return value of a v2 Builder to
* the filesystem.
@@ -100,17 +84,16 @@ async function writeBuildResultV2(
const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) {
await writeLambda(outputDir, output, normalizedPath, lambdas);
await writeLambda(outputDir, output, path, lambdas);
} else if (isPrerender(output)) {
await writeLambda(outputDir, output.lambda, normalizedPath, lambdas);
await writeLambda(outputDir, output.lambda, path, lambdas);
// Write the fallback file alongside the Lambda directory
let fallback = output.fallback;
if (fallback) {
const ext = getFileExtension(fallback);
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream();
await pipe(
@@ -126,7 +109,7 @@ async function writeBuildResultV2(
const prerenderConfigPath = join(
outputDir,
'functions',
`${normalizedPath}.prerender-config.json`
`${path}.prerender-config.json`
);
const prerenderConfig = {
...output,
@@ -135,20 +118,12 @@ async function writeBuildResultV2(
};
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) {
await writeStaticFile(
outputDir,
output,
normalizedPath,
overrides,
cleanUrls
);
await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, normalizedPath);
await writeEdgeFunction(outputDir, output, path);
} else {
throw new Error(
`Unsupported output type: "${
(output as any).type
}" for ${normalizedPath}`
`Unsupported output type: "${(output as any).type}" for ${path}`
);
}
}
@@ -170,9 +145,9 @@ async function writeBuildResultV3(
throw new Error(`Expected "build.src" to be a string`);
}
const ext = extname(src);
const path = stripDuplicateSlashes(
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
if (isLambda(output)) {
await writeLambda(outputDir, output, path);
} else if (isEdgeFunction(output)) {

View File

@@ -1,158 +0,0 @@
import fs from 'fs-extra';
import { join } from 'path';
import ini from 'ini';
import git from 'git-last-commit';
import { exec } from 'child_process';
import { GitMetadata, Project } from '../types';
import { Output } from './output';
export async function createGitMeta(
directory: string,
output: Output,
project?: Project | null
): Promise<GitMetadata | undefined> {
// If a Git repository is already connected via `vc git`, use that remote url
let remoteUrl;
if (project?.link) {
// in the form of org/repo
const { repo } = project.link;
const remoteUrls = await getRemoteUrls(
join(directory, '.git/config'),
output
);
if (remoteUrls) {
for (const urlValue of Object.values(remoteUrls)) {
if (urlValue.includes(repo)) {
remoteUrl = urlValue;
}
}
}
}
// If we couldn't get a remote url from the connected repo, default to the origin url
if (!remoteUrl) {
remoteUrl = await getOriginUrl(join(directory, '.git/config'), output);
}
// If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) {
return;
}
const [commit, dirty] = await Promise.all([
getLastCommit(directory).catch(err => {
output.debug(
`Failed to get last commit. The directory is likely not a Git repo, there are no latest commits, or it is corrupted.\n${err}`
);
return;
}),
isDirty(directory, output),
]);
if (!commit) {
return;
}
return {
remoteUrl,
commitAuthorName: commit.author.name,
commitMessage: commit.subject,
commitRef: commit.branch,
commitSha: commit.hash,
dirty,
};
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
export async function parseGitConfig(configPath: string, output: Output) {
try {
return ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
}
export function pluckRemoteUrls(gitConfig: {
[key: string]: any;
}): { [key: string]: string } | undefined {
let remoteUrls: { [key: string]: string } = {};
for (const key of Object.keys(gitConfig)) {
if (key.includes('remote')) {
// ex. remote "origin" — matches origin
const remoteName = key.match(/(?<=").*(?=")/g)?.[0];
const remoteUrl = gitConfig[key]?.url;
if (remoteName && remoteUrl) {
remoteUrls[remoteName] = remoteUrl;
}
}
}
if (Object.keys(remoteUrls).length === 0) {
return;
}
return remoteUrls;
}
export async function getRemoteUrls(
configPath: string,
output: Output
): Promise<{ [key: string]: string } | undefined> {
const config = await parseGitConfig(configPath, output);
if (!config) {
return;
}
const remoteUrls = pluckRemoteUrls(config);
return remoteUrls;
}
export function pluckOriginUrl(gitConfig: {
[key: string]: any;
}): string | undefined {
// Assuming "origin" is the remote url that the user would want to use
return gitConfig['remote "origin"']?.url;
}
export async function getOriginUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig = await parseGitConfig(configPath, output);
if (!gitConfig) {
return null;
}
const originUrl = pluckOriginUrl(gitConfig);
if (originUrl) {
return originUrl;
}
return null;
}

View File

@@ -0,0 +1,88 @@
import fs from 'fs-extra';
import { join } from 'path';
import ini from 'ini';
import git from 'git-last-commit';
import { exec } from 'child_process';
import { GitMetadata } from '../../types';
import { Output } from '../output';
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export async function getRemoteUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig;
try {
gitConfig = ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
if (!gitConfig) {
return null;
}
const originUrl: string = gitConfig['remote "origin"']?.url;
if (originUrl) {
return originUrl;
}
return null;
}
export async function createGitMeta(
directory: string,
output: Output
): Promise<GitMetadata | undefined> {
const remoteUrl = await getRemoteUrl(join(directory, '.git/config'), output);
// If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) {
return;
}
const [commit, dirty] = await Promise.all([
getLastCommit(directory).catch(err => {
output.debug(
`Failed to get last commit. The directory is likely not a Git repo, there are no latest commits, or it is corrupted.\n${err}`
);
return;
}),
isDirty(directory, output),
]);
if (!commit) {
return;
}
return {
remoteUrl,
commitAuthorName: commit.author.name,
commitMessage: commit.subject,
commitRef: commit.branch,
commitSha: commit.hash,
dirty,
};
}

View File

@@ -11,11 +11,14 @@ import cliPkg from '../pkg';
import cmd from '../output/cmd';
import { Output } from '../output';
import { getDistTag } from '../get-dist-tag';
import { NoBuilderCacheError } from '../errors-ts';
import * as staticBuilder from './static-builder';
import { BuilderWithPackage } from './types';
type CliPackageJson = typeof cliPkg;
const require_: typeof require = eval('require');
const registryTypes = new Set(['version', 'tag', 'range']);
@@ -34,6 +37,8 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
'@vercel/static': createStaticBuilder('vercel'),
};
const distTag = getDistTag(cliPkg.version);
export const cacheDirPromise = prepareCacheDir();
export const builderDirPromise = prepareBuilderDir();
@@ -97,8 +102,9 @@ function parseVersionSafe(rawSpec: string) {
export function filterPackage(
builderSpec: string,
distTag: string,
buildersPkg: PackageJson,
cliPkg: Partial<PackageJson>
cliPkg: Partial<CliPackageJson>
) {
if (builderSpec in localBuilders) return false;
const parsed = npa(builderSpec);
@@ -120,6 +126,31 @@ export function filterPackage(
return false;
}
// Skip install of already installed Runtime with tag compatible match
if (
parsed.name &&
parsed.type === 'tag' &&
parsed.fetchSpec === distTag &&
buildersPkg.dependencies
) {
const parsedInstalled = npa(
`${parsed.name}@${buildersPkg.dependencies[parsed.name]}`
);
if (parsedInstalled.type !== 'version') {
return true;
}
const semverInstalled = semver.parse(parsedInstalled.rawSpec);
if (!semverInstalled) {
return true;
}
if (semverInstalled.prerelease.length > 0) {
return semverInstalled.prerelease[0] !== distTag;
}
if (distTag === 'latest') {
return false;
}
}
return true;
}
@@ -152,7 +183,7 @@ export async function installBuilders(
// Filter out any packages that come packaged with Vercel CLI
const packagesToInstall = packages.filter(p =>
filterPackage(p, buildersPkgBefore, cliPkg)
filterPackage(p, distTag, buildersPkgBefore, cliPkg)
);
if (packagesToInstall.length === 0) {
@@ -361,13 +392,20 @@ export function isBundledBuilder(
return false;
}
const inCliDependencyList = !!dependencies[parsed.name];
const inScope = parsed.scope === '@vercel';
const isVersionedReference = ['tag', 'version', 'range'].includes(
parsed.type
);
const bundledVersion = dependencies[parsed.name];
if (bundledVersion) {
if (parsed.type === 'tag') {
if (parsed.fetchSpec === 'canary') {
return bundledVersion.includes('canary');
} else if (parsed.fetchSpec === 'latest') {
return !bundledVersion.includes('canary');
}
} else if (parsed.type === 'version') {
return parsed.fetchSpec === bundledVersion;
}
}
return inCliDependencyList && inScope && isVersionedReference;
return false;
}
function getPackageName(

View File

@@ -4,7 +4,7 @@ import ms from 'ms';
import bytes from 'bytes';
import { delimiter, dirname, join } from 'path';
import { fork, ChildProcess } from 'child_process';
import { createFunction } from '@vercel/fun';
import { createFunction } from '@zeit/fun';
import {
Builder,
BuildOptions,

View File

@@ -558,8 +558,9 @@ export default class DevServer {
]);
await this.validateVercelConfig(vercelConfig);
const { error: routeError, routes: maybeRoutes } =
getTransformedRoutes(vercelConfig);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
nowConfig: vercelConfig,
});
if (routeError) {
this.output.prettyError(routeError);
await this.exit();

View File

@@ -1,6 +1,6 @@
import http from 'http';
import { ChildProcess } from 'child_process';
import { Lambda as FunLambda } from '@vercel/fun';
import { Lambda as FunLambda } from '@zeit/fun';
import {
Builder as BuildConfig,
BuildOptions,

View File

@@ -87,18 +87,3 @@ export async function responseErrorMessage(
return `${message} (${res.status})`;
}
/**
* Returns a new Object with enumberable properties that match
* the provided `err` instance, for use with `JSON.stringify()`.
*/
export function toEnumerableError<E extends Partial<Error>>(err: E) {
const enumerable: {
[K in keyof E]?: E[K];
} = {};
enumerable.name = err.name;
for (const key of Object.getOwnPropertyNames(err) as (keyof E)[]) {
enumerable[key] = err[key];
}
return enumerable;
}

View File

@@ -14,7 +14,7 @@ interface ListSeparator {
separator: string;
}
export type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
interface ListOptions {
message: string;

View File

@@ -1,3 +1,4 @@
import inquirer from 'inquirer';
import Client from '../client';
import getUser from '../get-user';
import getTeams from '../teams/get-teams';
@@ -42,7 +43,7 @@ export default async function selectOrg(
return choices[defaultOrgIndex].value;
}
const answers = await client.prompt({
const answers = await inquirer.prompt({
type: 'list',
name: 'org',
message: question,

View File

@@ -1,117 +0,0 @@
import Client from '../client';
import { stringify } from 'qs';
import { Org } from '../../types';
import chalk from 'chalk';
import link from '../output/link';
export async function disconnectGitProvider(
client: Client,
org: Org,
projectId: string
) {
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
teamId: org.type === 'team' ? org.id : undefined,
})}`;
return client.fetch(fetchUrl, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
}
export async function connectGitProvider(
client: Client,
org: Org,
projectId: string,
type: string,
repo: string
) {
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
teamId: org.type === 'team' ? org.id : undefined,
})}`;
try {
return await client.fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type,
repo,
}),
});
} catch (err) {
if (
err.meta?.action === 'Install GitHub App' ||
err.code === 'repo_not_found'
) {
client.output.error(
`Failed to link ${chalk.cyan(
repo
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
);
} else if (err.action === 'Add a Login Connection') {
client.output.error(
err.message.replace(repo, chalk.cyan(repo)) +
`\nVisit ${link(err.link)} for more information.`
);
} else {
client.output.error(
`Failed to connect the ${formatProvider(
type
)} repository ${repo}.\n${err}`
);
}
return 1;
}
}
export function formatProvider(type: string): string {
switch (type) {
case 'github':
return 'GitHub';
case 'gitlab':
return 'GitLab';
case 'bitbucket':
return 'Bitbucket';
default:
return type;
}
}
export function parseRepoUrl(originUrl: string): {
provider: string;
org: string;
repo: string;
} | null {
const isSSH = originUrl.startsWith('git@');
// Matches all characters between (// or @) and (.com or .org)
// eslint-disable-next-line prefer-named-capture-group
const provider = /(?<=(\/\/|@)).*(?=(\.com|\.org))/.exec(originUrl);
if (!provider) {
return null;
}
let org;
let repo;
if (isSSH) {
org = originUrl.split(':')[1].split('/')[0];
repo = originUrl.split('/')[1]?.replace('.git', '');
} else {
// Assume https:// or git://
org = originUrl.split('/')[3];
repo = originUrl.split('/')[4]?.replace('.git', '');
}
if (!org || !repo) {
return null;
}
return {
provider: provider[0],
org,
repo,
};
}

View File

@@ -5,7 +5,6 @@ import { join } from 'path';
export type ProjectLinkAndSettings = ProjectLink & {
settings: {
createdAt: Project['createdAt'];
installCommand: Project['installCommand'];
buildCommand: Project['buildCommand'];
devCommand: Project['devCommand'];
@@ -29,7 +28,6 @@ export async function writeProjectSettings(
projectId: project.id,
orgId: org.id,
settings: {
createdAt: project.createdAt,
framework: project.framework,
devCommand: project.devCommand,
installCommand: project.installCommand,

View File

@@ -1,7 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
// nothing returned
}

View File

@@ -17,7 +17,6 @@ export default async function edge(request, event) {
decamelized: decamelize('someCamelCaseThing'),
uppercase: upper('someThing'),
optionalChaining: request?.doesnotexist ?? 'fallback',
ENV_VAR_IN_EDGE: process.env.ENV_VAR_IN_EDGE,
})
);
}

View File

@@ -1,3 +0,0 @@
export default function serverless(request, response) {
return response.send('hello from a serverless function');
}

View File

@@ -1,7 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
// no response
}

View File

@@ -16,11 +16,7 @@ const {
test('[vercel dev] should support edge functions', async () => {
const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir, {
env: {
ENV_VAR_IN_EDGE: '1',
},
});
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
@@ -46,7 +42,6 @@ test('[vercel dev] should support edge functions', async () => {
decamelized: 'some_camel_case_thing',
uppercase: 'SOMETHING',
optionalChaining: 'fallback',
ENV_VAR_IN_EDGE: '1',
});
} finally {
await dev.kill('SIGTERM');
@@ -61,31 +56,6 @@ test(
})
);
test('[vercel dev] throws an error when an edge function has no response', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-no-response`);
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
expect(await res.status).toBe(500);
expect(await res.text()).toMatch('FUNCTION_INVOCATION_FAILED');
expect(stdout).toMatch(
/Unhandled rejection: Edge Function "api\/edge-no-response.js" did not return a response./g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-no-response: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should support edge functions returning intentional 500 responses', async () => {
const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir);

View File

@@ -442,17 +442,6 @@ test(
})
);
test(
'[vercel dev] Middleware that has no response',
testFixtureStdio('middleware-no-response', async (testPath: any) => {
await testPath(
500,
'/api/hello',
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED'
);
})
);
test(
'[vercel dev] Middleware that does basic rewrite',
testFixtureStdio('middleware-rewrite', async (testPath: any) => {

View File

@@ -1,7 +0,0 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -1,5 +0,0 @@
{
"functions": {
"invalid.js": {}
}
}

View File

@@ -1,7 +0,0 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -1 +0,0 @@
export default (req, res) => res.end('Vercel');

View File

@@ -1 +0,0 @@
module.exports = (req, res) => res.end('Vercel');

View File

@@ -1 +0,0 @@
export default (req, res) => res.end('Vercel');

View File

@@ -1,4 +0,0 @@
import { IncomingMessage, ServerResponse } from 'http';
// Intentional syntax error to make the build fail
export default (req: IncomingMessage, res: ServerResponse => res.end('Vercel');

View File

@@ -1,9 +0,0 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null,
"buildCommand": null,
"outputDirectory": "out"
}
}

View File

@@ -1 +0,0 @@
<h1>Vercel</h1>

View File

@@ -1,5 +0,0 @@
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

View File

@@ -1,17 +1,9 @@
const { FileBlob, Lambda } = require('@vercel/build-utils');
const { FileBlob } = require('@vercel/build-utils');
exports.build = async () => {
const file = new FileBlob({
data: Buffer.from('file contents')
});
const lambda = new Lambda({
files: {},
runtime: 'provided',
handler: 'example.js'
})
const output = {
file,
'withTrailingSlash/': lambda
};
const output = { file };
return { output };
};

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "bad-remote-url"
}

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = bababooey
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "existing-connection"
}

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user2/repo2
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "invalid-repo"
}

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/laksfj/asdgklsadkl
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "multiple-remotes"
}

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,13 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2.git
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "new-connection"
}

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "no-git-config"
}

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "no-remote-url"
}

View File

@@ -1,7 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true

View File

@@ -1 +0,0 @@
!.vercel

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "new-connection"
}

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1,2 +0,0 @@
!.vercel
.vercel

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "connected-repo"
}

View File

@@ -1 +0,0 @@
add hi

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,13 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -1 +0,0 @@
8050816205303e5957b2909083c50677930d5b29

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,13 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -5,19 +5,17 @@ export function readOutputStream(
length: number = 3
): Promise<string> {
return new Promise((resolve, reject) => {
let output: string = '';
let lines = 0;
const chunks: Buffer[] = [];
const timeout = setTimeout(() => {
reject();
}, 3000);
client.stderr.resume();
client.stderr.on('data', chunk => {
output += chunk.toString();
lines++;
if (lines === length) {
chunks.push(chunk);
if (chunks.length === length) {
clearTimeout(timeout);
resolve(output);
resolve(chunks.toString().replace(/,/g, ''));
}
});
client.stderr.on('error', reject);

View File

@@ -7,17 +7,22 @@ import { Build, User } from '../../src/types';
let deployments = new Map<string, Deployment>();
let deploymentBuilds = new Map<Deployment, Build[]>();
type State = Deployment['readyState'];
export function useDeployment({
creator,
state = 'READY',
}: {
creator: Pick<User, 'id' | 'email' | 'name'>;
state?: State;
}) {
const createdAt = Date.now();
const url = new URL(chance().url());
const deployment: Deployment = {
id: `dpl_${chance().guid()}`,
url: url.hostname,
name: '',
name: chance.name,
meta: {},
regions: [],
routes: [],
@@ -26,13 +31,15 @@ export function useDeployment({
version: 2,
createdAt,
createdIn: 'sfo1',
buildingAt: Date.now(),
ownerId: creator.id,
creator: {
uid: creator.id,
email: creator.email,
username: creator.name,
},
readyState: 'READY',
readyState: state,
state: state,
env: {},
build: { env: {} },
target: 'production',

Some files were not shown because too many files have changed in this diff Show More