mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
54 Commits
@vercel/py
...
@vercel/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d3774ee7e | ||
|
|
50f8eec7cb | ||
|
|
45374e2f90 | ||
|
|
fd9142b6f3 | ||
|
|
8cf67b549b | ||
|
|
5dc6f48e44 | ||
|
|
66c8544e8f | ||
|
|
0140db38fa | ||
|
|
e5421c27e8 | ||
|
|
5afc527233 | ||
|
|
de9518b010 | ||
|
|
c322d1dbba | ||
|
|
18c19ead76 | ||
|
|
9d80c27382 | ||
|
|
bef1aec766 | ||
|
|
4f4a42813f | ||
|
|
181a492d91 | ||
|
|
1be7a80bb8 | ||
|
|
0428d4744e | ||
|
|
2a929a4bb9 | ||
|
|
accd308dc5 | ||
|
|
e2d4efab08 | ||
|
|
7e0dd6f808 | ||
|
|
8971e02e49 | ||
|
|
10c91c8579 | ||
|
|
bfdbe58675 | ||
|
|
7bdaf107b7 | ||
|
|
8de100f0e1 | ||
|
|
38a6785859 | ||
|
|
c67d1a8525 | ||
|
|
c5a7c574a2 | ||
|
|
d2f8d178f7 | ||
|
|
f9a747764c | ||
|
|
27d80f13cd | ||
|
|
8c668c925d | ||
|
|
4b1b33c143 | ||
|
|
a8d4147554 | ||
|
|
09339f494d | ||
|
|
ee4d772ae9 | ||
|
|
61e8103404 | ||
|
|
fb4f477325 | ||
|
|
016bff848e | ||
|
|
183e411f7c | ||
|
|
070e300148 | ||
|
|
cbdf9b4a88 | ||
|
|
ec9b55dc81 | ||
|
|
06829bc21a | ||
|
|
628071f659 | ||
|
|
5a7461dfe3 | ||
|
|
599f8f675c | ||
|
|
0a8bc494fc | ||
|
|
34e008f42e | ||
|
|
037633b3f1 | ||
|
|
1a6f3c0270 |
@@ -1,4 +1,5 @@
|
||||
# https://prettier.io/docs/en/ignore.html
|
||||
|
||||
# ignore this file with an intentional syntax error
|
||||
# ignore these files 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
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"description": "API for the vercel/vercel repo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"vercel-build": "node ../utils/run.js build all"
|
||||
"//TODO": "We should add this pkg to yarn workspaces",
|
||||
"vercel-build": "cd .. && yarn install && yarn vercel-build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
"got": "10.2.1",
|
||||
"node-fetch": "2.6.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"parse-github-url": "1.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"unzip-stream": "0.3.0"
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
"jest": "28.0.2",
|
||||
"json5": "2.1.1",
|
||||
"lint-staged": "9.2.5",
|
||||
"node-fetch": "2.6.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"prettier": "2.6.2",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "28.0.5",
|
||||
"turbo": "1.3.1"
|
||||
"turbo": "1.3.2-canary.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.0.1",
|
||||
"version": "5.0.4",
|
||||
"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.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"semver": "6.1.1",
|
||||
"typescript": "4.3.4",
|
||||
"yazl": "2.5.1"
|
||||
|
||||
@@ -33,6 +33,11 @@ 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;
|
||||
@@ -40,5 +45,6 @@ export class EdgeFunction {
|
||||
this.entrypoint = params.entrypoint;
|
||||
this.files = params.files;
|
||||
this.envVarsInUse = params.envVarsInUse;
|
||||
this.assets = params.assets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,6 @@ 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];
|
||||
}
|
||||
@@ -75,7 +72,7 @@ export async function getSupportedNodeVersion(
|
||||
throw new NowBuildError({
|
||||
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
|
||||
link: 'http://vercel.link/node-version',
|
||||
message: `${intro} ${getHint(isAuto)} ${upstreamProvider}`,
|
||||
message: `${intro} ${getHint(isAuto)}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,9 +83,9 @@ export async function getSupportedNodeVersion(
|
||||
console.warn(
|
||||
`Error: Node.js version ${
|
||||
selection.range
|
||||
} is deprecated. Deployments created on or after ${d} will fail to build. ${getHint(
|
||||
} has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
|
||||
isAuto
|
||||
)} ${upstreamProvider}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
56
packages/build-utils/test/unit.test.ts
vendored
56
packages/build-utils/test/unit.test.ts
vendored
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -386,10 +387,10 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
12
|
||||
);
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'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).',
|
||||
'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.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
@@ -494,28 +495,43 @@ 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 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);
|
||||
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);
|
||||
});
|
||||
|
||||
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');
|
||||
const [run1, run2, run3] = await Promise.all([
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(apiDir, [], undefined, meta),
|
||||
runNpmInstall(fixture, [], undefined, meta),
|
||||
]);
|
||||
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 || [];
|
||||
expect(run1).toEqual(true);
|
||||
expect(run2).toEqual(false);
|
||||
expect(run3).toEqual(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "27.0.0",
|
||||
"version": "27.2.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.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",
|
||||
"@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",
|
||||
"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.0",
|
||||
"@vercel/frameworks": "1.1.0",
|
||||
"@vercel/fs-detectors": "1.0.1",
|
||||
"@vercel/client": "12.1.3",
|
||||
"@vercel/frameworks": "1.1.1",
|
||||
"@vercel/fs-detectors": "2.0.1",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@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.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"open": "8.4.0",
|
||||
"ora": "3.4.0",
|
||||
|
||||
@@ -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 `@zeit/fun`'s runtime files:
|
||||
// `ncc` has some issues with `@vercel/fun`'s runtime files:
|
||||
// - Executable bits on the `bootstrap` files appear to be lost:
|
||||
// https://github.com/zeit/ncc/pull/182
|
||||
// https://github.com/vercel/ncc/pull/182
|
||||
// - The `bootstrap.js` asset does not get copied into the output dir:
|
||||
// https://github.com/zeit/ncc/issues/278
|
||||
// https://github.com/vercel/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/@zeit/fun/dist/src/runtimes'
|
||||
'../../node_modules/@vercel/fun/dist/src/runtimes'
|
||||
);
|
||||
await cpy('**/*', join(distRoot, 'runtimes'), {
|
||||
parents: true,
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
MergeRoutesProps,
|
||||
Route,
|
||||
} from '@vercel/routing-utils';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
import type { VercelConfig } from '@vercel/client';
|
||||
|
||||
import pull from './pull';
|
||||
import { staticFiles as getFiles } from '../util/get-files';
|
||||
@@ -36,7 +36,10 @@ 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 { readProjectSettings } from '../util/projects/project-settings';
|
||||
import {
|
||||
ProjectLinkAndSettings,
|
||||
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';
|
||||
@@ -46,11 +49,31 @@ import {
|
||||
PathOverride,
|
||||
writeBuildResult,
|
||||
} from '../util/build/write-build-result';
|
||||
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
|
||||
import { importBuilders } 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`)}
|
||||
@@ -167,7 +190,6 @@ 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`),
|
||||
@@ -181,6 +203,48 @@ 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
|
||||
@@ -198,19 +262,18 @@ export default async function main(client: Client): Promise<number> {
|
||||
normalizePath(relative(workPath, f))
|
||||
);
|
||||
|
||||
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
|
||||
const routesResult = getTransformedRoutes(vercelConfig || {});
|
||||
if (routesResult.error) {
|
||||
output.prettyError(routesResult.error);
|
||||
return 1;
|
||||
throw routesResult.error;
|
||||
}
|
||||
|
||||
if (vercelConfig?.builds && vercelConfig.functions) {
|
||||
output.prettyError({
|
||||
throw new NowBuildError({
|
||||
code: 'bad_request',
|
||||
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 || [];
|
||||
@@ -228,12 +291,12 @@ export default async function main(client: Client): Promise<number> {
|
||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||
...vercelConfig,
|
||||
projectSettings: project.settings,
|
||||
ignoreBuildScript: true,
|
||||
featHandleMiss: true,
|
||||
});
|
||||
|
||||
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
|
||||
output.prettyError(detectedBuilders.errors[0]);
|
||||
return 1;
|
||||
throw detectedBuilders.errors[0];
|
||||
}
|
||||
|
||||
for (const w of detectedBuilders.warnings) {
|
||||
@@ -266,13 +329,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
const builderSpecs = new Set(builds.map(b => b.use));
|
||||
|
||||
let buildersWithPkgs: Map<string, BuilderWithPkg>;
|
||||
try {
|
||||
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
return 1;
|
||||
}
|
||||
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||
|
||||
// Populate Files -> FileFsRef mapping
|
||||
const filesMap: Files = {};
|
||||
@@ -282,12 +339,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
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
|
||||
@@ -296,32 +347,31 @@ export default async function main(client: Client): Promise<number> {
|
||||
const ops: Promise<Error | void>[] = [];
|
||||
|
||||
// Write the `detectedBuilders` result to output dir
|
||||
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 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}"`);
|
||||
}
|
||||
)
|
||||
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
|
||||
@@ -332,65 +382,87 @@ export default async function main(client: Client): Promise<number> {
|
||||
};
|
||||
|
||||
// Execute Builders for detected entrypoints
|
||||
// TODO: parallelize builds
|
||||
// TODO: parallelize builds (except for frontend)
|
||||
const sortedBuilders = sortBuilders(builds);
|
||||
const buildResults: Map<Builder, BuildResult> = new Map();
|
||||
const overrides: PathOverride[] = [];
|
||||
const repoRootPath = cwd;
|
||||
const rootPackageJsonPath = repoRootPath || workPath;
|
||||
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
|
||||
const corepackShimDir = await initCorepack({ repoRootPath });
|
||||
|
||||
for (const build of builds) {
|
||||
for (const build of sortedBuilders) {
|
||||
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;
|
||||
|
||||
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);
|
||||
try {
|
||||
const { builder, pkg: builderPkg } = builderWithPkg;
|
||||
|
||||
// Store the build result to generate the final `config.json` after
|
||||
// all builds have completed
|
||||
buildResults.set(build, buildResult);
|
||||
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);
|
||||
|
||||
// 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
|
||||
)
|
||||
);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (corepackShimDir) {
|
||||
@@ -399,15 +471,12 @@ export default async function main(client: Client): Promise<number> {
|
||||
|
||||
// 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) {
|
||||
hadError = true;
|
||||
output.prettyError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (hadError) return 1;
|
||||
|
||||
// Merge existing `config.json` file into the one that will be produced
|
||||
const configPath = join(outputDir, 'config.json');
|
||||
|
||||
@@ -15,6 +15,7 @@ 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
|
||||
|
||||
@@ -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/deploy/create-git-meta';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
|
||||
export default async (client: Client) => {
|
||||
const { output } = client;
|
||||
@@ -95,6 +95,7 @@ export default async (client: Client) => {
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--no-clipboard': Boolean,
|
||||
'--target': String,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -183,6 +184,17 @@ 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') {
|
||||
@@ -416,7 +428,7 @@ export default async (client: Client) => {
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
const gitMetadata = await createGitMeta(path, output);
|
||||
const gitMetadata = await createGitMeta(path, output, project);
|
||||
|
||||
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
||||
const deploymentEnv = Object.assign(
|
||||
|
||||
21
packages/cli/src/commands/env/index.ts
vendored
21
packages/cli/src/commands/env/index.ts
vendored
@@ -1,7 +1,9 @@
|
||||
import chalk from 'chalk';
|
||||
import { ProjectEnvTarget } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
|
||||
import {
|
||||
getEnvTargetPlaceholder,
|
||||
isValidEnvTarget,
|
||||
} from '../../util/env/env-target';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
@@ -29,6 +31,7 @@ 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
|
||||
@@ -111,6 +114,7 @@ export default async function main(client: Client) {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
'--environment': String,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
@@ -126,6 +130,17 @@ 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;
|
||||
@@ -150,7 +165,7 @@ export default async function main(client: Client) {
|
||||
return pull(
|
||||
client,
|
||||
project,
|
||||
ProjectEnvTarget.Development,
|
||||
target,
|
||||
argv,
|
||||
args,
|
||||
output,
|
||||
|
||||
205
packages/cli/src/commands/git/connect.ts
Normal file
205
packages/cli/src/commands/git/connect.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
58
packages/cli/src/commands/git/disconnect.ts
Normal file
58
packages/cli/src/commands/git/disconnect.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
}
|
||||
94
packages/cli/src/commands/git/index.ts
Normal file
94
packages/cli/src/commands/git/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export default new Map([
|
||||
['domain', 'domains'],
|
||||
['domains', 'domains'],
|
||||
['env', 'env'],
|
||||
['git', 'git'],
|
||||
['help', 'help'],
|
||||
['init', 'init'],
|
||||
['inspect', 'inspect'],
|
||||
@@ -25,8 +26,8 @@ export default new Map([
|
||||
['logout', 'logout'],
|
||||
['logs', 'logs'],
|
||||
['ls', 'list'],
|
||||
['project', 'projects'],
|
||||
['projects', 'projects'],
|
||||
['project', 'project'],
|
||||
['projects', 'project'],
|
||||
['pull', 'pull'],
|
||||
['remove', 'remove'],
|
||||
['rm', 'remove'],
|
||||
|
||||
55
packages/cli/src/commands/project/add.ts
Normal file
55
packages/cli/src/commands/project/add.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import Client from '../../util/client';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
export default async function add(
|
||||
client: Client,
|
||||
args: string[],
|
||||
contextName: string
|
||||
) {
|
||||
const { output } = client;
|
||||
if (args.length !== 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project add <name>')}`
|
||||
)}`
|
||||
);
|
||||
|
||||
if (args.length > 1) {
|
||||
const example = chalk.cyan(
|
||||
`${getCommandName(`project add "${args.join(' ')}"`)}`
|
||||
);
|
||||
output.log(
|
||||
`If your project name has spaces, make sure to wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
const [name] = args;
|
||||
try {
|
||||
await client.fetch('/projects', {
|
||||
method: 'POST',
|
||||
body: { name },
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 409) {
|
||||
// project already exists, so we can
|
||||
// show a success message
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
output.log(
|
||||
`${chalk.cyan('Success!')} Project ${chalk.bold(
|
||||
name.toLowerCase()
|
||||
)} added (${chalk.bold(contextName)}) ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
102
packages/cli/src/commands/project/index.ts
Normal file
102
packages/cli/src/commands/project/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import chalk from 'chalk';
|
||||
import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
||||
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 add from './add';
|
||||
import list from './list';
|
||||
import rm from './rm';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} project`)} [options] <command>
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
ls Show all projects in the selected team/user
|
||||
add [name] Add a new project
|
||||
rm [name] Remove a project
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} project add my-project`)}
|
||||
|
||||
${chalk.gray('–')} Paginate projects, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} project ls --next 1584722256178`)}
|
||||
`);
|
||||
};
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
ls: ['ls', 'list'],
|
||||
add: ['add'],
|
||||
rm: ['rm', 'remove'],
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
let argv: any;
|
||||
let subcommand: string | string[];
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
subcommand = argv._[0] || 'list';
|
||||
const args = argv._.slice(1);
|
||||
const { output } = client;
|
||||
|
||||
let contextName = '';
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (error) {
|
||||
if (error.code === 'NOT_AUTHORIZED' || error.code === 'TEAM_DELETED') {
|
||||
output.error(error.message);
|
||||
return 1;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case 'ls':
|
||||
case 'list':
|
||||
return await list(client, argv, args, contextName);
|
||||
case 'add':
|
||||
return await add(client, args, contextName);
|
||||
case 'rm':
|
||||
case 'remove':
|
||||
return await rm(client, args);
|
||||
default:
|
||||
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
86
packages/cli/src/commands/project/list.ts
Normal file
86
packages/cli/src/commands/project/list.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Client from '../../util/client';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import strlen from '../../util/strlen';
|
||||
|
||||
export default async function list(
|
||||
client: Client,
|
||||
argv: any,
|
||||
args: string[],
|
||||
contextName: string
|
||||
) {
|
||||
const { output } = client;
|
||||
if (args.length !== 0) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project ls')}`
|
||||
)}`
|
||||
);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
output.spinner(`Fetching projects in ${chalk.bold(contextName)}`);
|
||||
|
||||
let projectsUrl = '/v4/projects/?limit=20';
|
||||
|
||||
const next = argv['--next'] || false;
|
||||
if (next) {
|
||||
projectsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
const {
|
||||
projects: list,
|
||||
pagination,
|
||||
}: {
|
||||
projects: [{ name: string; updatedAt: number }];
|
||||
pagination: { count: number; next: number };
|
||||
} = await client.fetch(projectsUrl, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
output.stopSpinner();
|
||||
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
output.log(
|
||||
`${list.length > 0 ? 'Projects' : 'No projects'} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
|
||||
if (list.length > 0) {
|
||||
const cur = Date.now();
|
||||
const header = [['', 'name', 'updated'].map(title => chalk.dim(title))];
|
||||
|
||||
const out = table(
|
||||
header.concat(
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - secret.updatedAt)} ago`),
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
if (out) {
|
||||
output.print(`\n${out}\n\n`);
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||
const nextCmd = `project ls${flags} --next ${pagination.next}`;
|
||||
output.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
63
packages/cli/src/commands/project/rm.ts
Normal file
63
packages/cli/src/commands/project/rm.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import Client from '../../util/client';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
const e = encodeURIComponent;
|
||||
|
||||
export default async function rm(client: Client, args: string[]) {
|
||||
if (args.length !== 1) {
|
||||
client.output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project rm <name>')}`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const name = args[0];
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
const yes = await readConfirmation(client, name);
|
||||
|
||||
if (!yes) {
|
||||
client.output.log('User abort');
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
await client.fetch(`/v2/projects/${e(name)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
client.output.error('No such project exists');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
const elapsed = ms(Date.now() - start);
|
||||
client.output.log(
|
||||
`${chalk.cyan('Success!')} Project ${chalk.bold(name)} removed ${chalk.gray(
|
||||
`[${elapsed}]`
|
||||
)}`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function readConfirmation(
|
||||
client: Client,
|
||||
projectName: string
|
||||
): Promise<boolean> {
|
||||
client.output.print(
|
||||
prependEmoji(
|
||||
`The project ${chalk.bold(projectName)} will be removed permanently.\n` +
|
||||
`It will also delete everything under the project including deployments.\n`,
|
||||
emoji('warning')
|
||||
)
|
||||
);
|
||||
|
||||
return await confirm(client, `${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import strlen from '../util/strlen';
|
||||
import getArgs from '../util/get-args';
|
||||
import { handleError, error } from '../util/error';
|
||||
import exit from '../util/exit';
|
||||
import logo from '../util/output/logo';
|
||||
import getScope from '../util/get-scope';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import Client from '../util/client';
|
||||
|
||||
const e = encodeURIComponent;
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} projects`)} [options] <command>
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
ls Show all projects in the selected team/user
|
||||
add [name] Add a new project
|
||||
rm [name] Remove a project
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new project
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} projects add my-project`)}
|
||||
|
||||
${chalk.gray('–')} Paginate projects, where ${chalk.dim(
|
||||
'`1584722256178`'
|
||||
)} is the time in milliseconds since the UNIX epoch.
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} projects ls --next 1584722256178`)}
|
||||
`);
|
||||
};
|
||||
|
||||
let argv: any;
|
||||
let subcommand: string | string[];
|
||||
|
||||
const main = async (client: Client) => {
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
subcommand = argv._[0] || 'list';
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return exit(2);
|
||||
}
|
||||
|
||||
const { output } = client;
|
||||
|
||||
let contextName = null;
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
await run({ client, contextName });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export default async (client: Client) => {
|
||||
try {
|
||||
await main(client);
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
async function run({
|
||||
client,
|
||||
contextName,
|
||||
}: {
|
||||
client: Client;
|
||||
contextName: string;
|
||||
}) {
|
||||
const { output } = client;
|
||||
const args = argv._.slice(1);
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
if (subcommand === 'ls' || subcommand === 'list') {
|
||||
if (args.length !== 0) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('projects ls')}`
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(2);
|
||||
}
|
||||
|
||||
output.spinner(`Fetching projects in ${chalk.bold(contextName)}`);
|
||||
|
||||
let projectsUrl = '/v4/projects/?limit=20';
|
||||
|
||||
const next = argv['--next'];
|
||||
if (next) {
|
||||
projectsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
const {
|
||||
projects: list,
|
||||
pagination,
|
||||
}: {
|
||||
projects: [{ name: string; updatedAt: number }];
|
||||
pagination: { count: number; next: number };
|
||||
} = await client.fetch(projectsUrl, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
output.stopSpinner();
|
||||
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
console.log(
|
||||
`> ${
|
||||
list.length > 0 ? 'Projects' : 'No projects'
|
||||
} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
|
||||
if (list.length > 0) {
|
||||
const cur = Date.now();
|
||||
const header = [['', 'name', 'updated'].map(title => chalk.dim(title))];
|
||||
|
||||
const out = table(
|
||||
header.concat(
|
||||
list.map(secret => [
|
||||
'',
|
||||
chalk.bold(secret.name),
|
||||
chalk.gray(`${ms(cur - secret.updatedAt)} ago`),
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l'],
|
||||
hsep: ' '.repeat(2),
|
||||
stringLength: strlen,
|
||||
}
|
||||
);
|
||||
|
||||
if (out) {
|
||||
console.log(`\n${out}\n`);
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||
const nextCmd = `projects ls${flags} --next ${pagination.next}`;
|
||||
console.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === 'rm' || subcommand === 'remove') {
|
||||
if (args.length !== 1) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project rm <name>')}`
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const name = args[0];
|
||||
|
||||
const yes = await readConfirmation(name);
|
||||
if (!yes) {
|
||||
console.error(error('User abort'));
|
||||
return exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
await client.fetch(`/v2/projects/${e(name)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
console.error(error('No such project exists'));
|
||||
return exit(1);
|
||||
}
|
||||
}
|
||||
const elapsed = ms(Date.now() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||
name
|
||||
)} removed ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === 'add') {
|
||||
if (args.length !== 1) {
|
||||
console.error(
|
||||
error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('projects add <name>')}`
|
||||
)}`
|
||||
)
|
||||
);
|
||||
|
||||
if (args.length > 1) {
|
||||
const example = chalk.cyan(
|
||||
`${getCommandName(`projects add "${args.join(' ')}"`)}`
|
||||
);
|
||||
console.log(
|
||||
`> If your project name has spaces, make sure to wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
}
|
||||
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const [name] = args;
|
||||
try {
|
||||
await client.fetch('/projects', {
|
||||
method: 'POST',
|
||||
body: { name },
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 409) {
|
||||
// project already exists, so we can
|
||||
// show a success message
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const elapsed = ms(Date.now() - start);
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||
name.toLowerCase()
|
||||
)} added (${chalk.bold(contextName)}) ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||
help();
|
||||
exit(2);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
handleError(err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
function readConfirmation(projectName: string) {
|
||||
return new Promise(resolve => {
|
||||
process.stdout.write(
|
||||
`The project: ${chalk.bold(projectName)} will be removed permanently.\n` +
|
||||
`It will also delete everything under the project including deployments.\n`
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
);
|
||||
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
process.stdin.pause();
|
||||
resolve(d.toString().trim().toLowerCase() === 'y');
|
||||
})
|
||||
.resume();
|
||||
});
|
||||
}
|
||||
@@ -172,7 +172,8 @@ const main = async () => {
|
||||
// * a subcommand (as in: `vercel ls`)
|
||||
const targetOrSubcommand = argv._[2];
|
||||
|
||||
const betaCommands: string[] = ['build'];
|
||||
// Currently no beta commands - add here as needed
|
||||
const betaCommands: string[] = [];
|
||||
if (betaCommands.includes(targetOrSubcommand)) {
|
||||
console.log(
|
||||
`${chalk.grey(
|
||||
@@ -631,6 +632,9 @@ 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;
|
||||
@@ -652,8 +656,8 @@ const main = async () => {
|
||||
case 'logout':
|
||||
func = require('./commands/logout').default;
|
||||
break;
|
||||
case 'projects':
|
||||
func = require('./commands/projects').default;
|
||||
case 'project':
|
||||
func = require('./commands/project').default;
|
||||
break;
|
||||
case 'pull':
|
||||
func = require('./commands/pull').default;
|
||||
@@ -746,9 +750,7 @@ 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.stack}`
|
||||
);
|
||||
output.error(`An unexpected error occurred in ${subcommand}: ${err}`);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@@ -248,12 +248,34 @@ 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>[];
|
||||
}
|
||||
|
||||
@@ -6,11 +6,9 @@ import { VERCEL_DIR } from '../projects/link';
|
||||
import readJSONFile from '../read-json-file';
|
||||
|
||||
export async function initCorepack({
|
||||
cwd,
|
||||
rootPackageJsonPath,
|
||||
repoRootPath,
|
||||
}: {
|
||||
cwd: string;
|
||||
rootPackageJsonPath: string;
|
||||
repoRootPath: string;
|
||||
}): Promise<string | null> {
|
||||
if (process.env.ENABLE_EXPERIMENTAL_COREPACK !== '1') {
|
||||
// Since corepack is experimental, we need to exit early
|
||||
@@ -18,7 +16,7 @@ export async function initCorepack({
|
||||
return null;
|
||||
}
|
||||
const pkg = await readJSONFile<PackageJson>(
|
||||
join(rootPackageJsonPath, 'package.json')
|
||||
join(repoRootPath, 'package.json')
|
||||
);
|
||||
if (pkg instanceof CantParseJSONFile) {
|
||||
console.warn(
|
||||
@@ -32,16 +30,13 @@ export async function initCorepack({
|
||||
console.log(
|
||||
`Detected ENABLE_EXPERIMENTAL_COREPACK=1 and "${pkg.packageManager}" in package.json`
|
||||
);
|
||||
const corepackRootDir = join(cwd, VERCEL_DIR, 'cache', 'corepack');
|
||||
const corepackRootDir = join(repoRootPath, 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
|
||||
@@ -72,11 +67,4 @@ 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,', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
packages/cli/src/util/build/sort-builders.ts
Normal file
12
packages/cli/src/util/build/sort-builders.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
import fs from 'fs-extra';
|
||||
import mimeTypes from 'mime-types';
|
||||
import { basename, dirname, extname, join, relative, resolve } from 'path';
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
extname,
|
||||
join,
|
||||
relative,
|
||||
resolve,
|
||||
posix,
|
||||
} from 'path';
|
||||
import {
|
||||
Builder,
|
||||
BuildResultV2,
|
||||
@@ -20,6 +28,7 @@ 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(
|
||||
@@ -67,6 +76,13 @@ 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.
|
||||
@@ -84,16 +100,17 @@ 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, path, lambdas);
|
||||
await writeLambda(outputDir, output, normalizedPath, lambdas);
|
||||
} else if (isPrerender(output)) {
|
||||
await writeLambda(outputDir, output.lambda, path, lambdas);
|
||||
await writeLambda(outputDir, output.lambda, normalizedPath, lambdas);
|
||||
|
||||
// Write the fallback file alongside the Lambda directory
|
||||
let fallback = output.fallback;
|
||||
if (fallback) {
|
||||
const ext = getFileExtension(fallback);
|
||||
const fallbackName = `${path}.prerender-fallback${ext}`;
|
||||
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
|
||||
const fallbackPath = join(outputDir, 'functions', fallbackName);
|
||||
const stream = fallback.toStream();
|
||||
await pipe(
|
||||
@@ -109,7 +126,7 @@ async function writeBuildResultV2(
|
||||
const prerenderConfigPath = join(
|
||||
outputDir,
|
||||
'functions',
|
||||
`${path}.prerender-config.json`
|
||||
`${normalizedPath}.prerender-config.json`
|
||||
);
|
||||
const prerenderConfig = {
|
||||
...output,
|
||||
@@ -118,12 +135,20 @@ async function writeBuildResultV2(
|
||||
};
|
||||
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
|
||||
} else if (isFile(output)) {
|
||||
await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
|
||||
await writeStaticFile(
|
||||
outputDir,
|
||||
output,
|
||||
normalizedPath,
|
||||
overrides,
|
||||
cleanUrls
|
||||
);
|
||||
} else if (isEdgeFunction(output)) {
|
||||
await writeEdgeFunction(outputDir, output, path);
|
||||
await writeEdgeFunction(outputDir, output, normalizedPath);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported output type: "${(output as any).type}" for ${path}`
|
||||
`Unsupported output type: "${
|
||||
(output as any).type
|
||||
}" for ${normalizedPath}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -145,9 +170,9 @@ async function writeBuildResultV3(
|
||||
throw new Error(`Expected "build.src" to be a string`);
|
||||
}
|
||||
const ext = extname(src);
|
||||
const path = build.config?.zeroConfig
|
||||
? src.substring(0, src.length - ext.length)
|
||||
: src;
|
||||
const path = stripDuplicateSlashes(
|
||||
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
|
||||
);
|
||||
if (isLambda(output)) {
|
||||
await writeLambda(outputDir, output, path);
|
||||
} else if (isEdgeFunction(output)) {
|
||||
|
||||
158
packages/cli/src/util/create-git-meta.ts
Normal file
158
packages/cli/src/util/create-git-meta.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,79 +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 } from '../../types';
|
||||
import { Output } from '../output';
|
||||
|
||||
export function isDirty(directory: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
|
||||
if (err) return reject(err);
|
||||
if (stderr)
|
||||
return reject(
|
||||
new Error(
|
||||
`Failed to determine if git repo has been modified: ${stderr.trim()}`
|
||||
)
|
||||
);
|
||||
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),
|
||||
isDirty(directory),
|
||||
]);
|
||||
|
||||
return {
|
||||
remoteUrl,
|
||||
commitAuthorName: commit.author.name,
|
||||
commitMessage: commit.subject,
|
||||
commitRef: commit.branch,
|
||||
commitSha: commit.hash,
|
||||
dirty,
|
||||
};
|
||||
}
|
||||
@@ -11,14 +11,11 @@ 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']);
|
||||
@@ -37,8 +34,6 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
|
||||
'@vercel/static': createStaticBuilder('vercel'),
|
||||
};
|
||||
|
||||
const distTag = getDistTag(cliPkg.version);
|
||||
|
||||
export const cacheDirPromise = prepareCacheDir();
|
||||
export const builderDirPromise = prepareBuilderDir();
|
||||
|
||||
@@ -102,9 +97,8 @@ function parseVersionSafe(rawSpec: string) {
|
||||
|
||||
export function filterPackage(
|
||||
builderSpec: string,
|
||||
distTag: string,
|
||||
buildersPkg: PackageJson,
|
||||
cliPkg: Partial<CliPackageJson>
|
||||
cliPkg: Partial<PackageJson>
|
||||
) {
|
||||
if (builderSpec in localBuilders) return false;
|
||||
const parsed = npa(builderSpec);
|
||||
@@ -126,31 +120,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -183,7 +152,7 @@ export async function installBuilders(
|
||||
|
||||
// Filter out any packages that come packaged with Vercel CLI
|
||||
const packagesToInstall = packages.filter(p =>
|
||||
filterPackage(p, distTag, buildersPkgBefore, cliPkg)
|
||||
filterPackage(p, buildersPkgBefore, cliPkg)
|
||||
);
|
||||
|
||||
if (packagesToInstall.length === 0) {
|
||||
@@ -392,20 +361,13 @@ export function isBundledBuilder(
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
const inCliDependencyList = !!dependencies[parsed.name];
|
||||
const inScope = parsed.scope === '@vercel';
|
||||
const isVersionedReference = ['tag', 'version', 'range'].includes(
|
||||
parsed.type
|
||||
);
|
||||
|
||||
return false;
|
||||
return inCliDependencyList && inScope && isVersionedReference;
|
||||
}
|
||||
|
||||
function getPackageName(
|
||||
|
||||
@@ -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 '@zeit/fun';
|
||||
import { createFunction } from '@vercel/fun';
|
||||
import {
|
||||
Builder,
|
||||
BuildOptions,
|
||||
|
||||
@@ -558,9 +558,8 @@ export default class DevServer {
|
||||
]);
|
||||
|
||||
await this.validateVercelConfig(vercelConfig);
|
||||
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
||||
nowConfig: vercelConfig,
|
||||
});
|
||||
const { error: routeError, routes: maybeRoutes } =
|
||||
getTransformedRoutes(vercelConfig);
|
||||
if (routeError) {
|
||||
this.output.prettyError(routeError);
|
||||
await this.exit();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import http from 'http';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { Lambda as FunLambda } from '@zeit/fun';
|
||||
import { Lambda as FunLambda } from '@vercel/fun';
|
||||
import {
|
||||
Builder as BuildConfig,
|
||||
BuildOptions,
|
||||
|
||||
@@ -87,3 +87,18 @@ 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;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface ListSeparator {
|
||||
separator: string;
|
||||
}
|
||||
|
||||
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
export type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
|
||||
interface ListOptions {
|
||||
message: string;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import inquirer from 'inquirer';
|
||||
import Client from '../client';
|
||||
import getUser from '../get-user';
|
||||
import getTeams from '../teams/get-teams';
|
||||
@@ -43,7 +42,7 @@ export default async function selectOrg(
|
||||
return choices[defaultOrgIndex].value;
|
||||
}
|
||||
|
||||
const answers = await inquirer.prompt({
|
||||
const answers = await client.prompt({
|
||||
type: 'list',
|
||||
name: 'org',
|
||||
message: question,
|
||||
|
||||
117
packages/cli/src/util/projects/connect-git-provider.ts
Normal file
117
packages/cli/src/util/projects/connect-git-provider.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { join } from 'path';
|
||||
|
||||
export type ProjectLinkAndSettings = ProjectLink & {
|
||||
settings: {
|
||||
createdAt: Project['createdAt'];
|
||||
installCommand: Project['installCommand'];
|
||||
buildCommand: Project['buildCommand'];
|
||||
devCommand: Project['devCommand'];
|
||||
@@ -28,6 +29,7 @@ export async function writeProjectSettings(
|
||||
projectId: project.id,
|
||||
orgId: org.id,
|
||||
settings: {
|
||||
createdAt: project.createdAt,
|
||||
framework: project.framework,
|
||||
devCommand: project.devCommand,
|
||||
installCommand: project.installCommand,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
};
|
||||
|
||||
export default async function edge(request, event) {
|
||||
// nothing returned
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function serverless(request, response) {
|
||||
return response.send('hello from a serverless function');
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
};
|
||||
|
||||
export default async function edge(request, event) {
|
||||
// no response
|
||||
}
|
||||
@@ -16,7 +16,11 @@ const {
|
||||
|
||||
test('[vercel dev] should support edge functions', async () => {
|
||||
const dir = fixture('edge-function');
|
||||
const { dev, port, readyResolver } = await testFixture(dir);
|
||||
const { dev, port, readyResolver } = await testFixture(dir, {
|
||||
env: {
|
||||
ENV_VAR_IN_EDGE: '1',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await readyResolver;
|
||||
@@ -42,6 +46,7 @@ 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');
|
||||
@@ -56,6 +61,31 @@ 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);
|
||||
|
||||
@@ -442,6 +442,17 @@ 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) => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": null
|
||||
}
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/build/error-vercel-json-validation/index.html
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/error-vercel-json-validation/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Vercel</h1>
|
||||
5
packages/cli/test/fixtures/unit/commands/build/error-vercel-json-validation/vercel.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/error-vercel-json-validation/vercel.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"functions": {
|
||||
"invalid.js": {}
|
||||
}
|
||||
}
|
||||
7
packages/cli/test/fixtures/unit/commands/build/node-error/.vercel/project.json
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/build/node-error/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": null
|
||||
}
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/es6.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/es6.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default (req, res) => res.end('Vercel');
|
||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/index.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.end('Vercel');
|
||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/mjs.mjs
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/mjs.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default (req, res) => res.end('Vercel');
|
||||
4
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
|
||||
// Intentional syntax error to make the build fail
|
||||
export default (req: IncomingMessage, res: ServerResponse => res.end('Vercel');
|
||||
9
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/.vercel/project.json
vendored
Normal file
9
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": null,
|
||||
"buildCommand": null,
|
||||
"outputDirectory": "out"
|
||||
}
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/index.html
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Vercel</h1>
|
||||
5
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/package.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/static-with-pkg/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
const { FileBlob } = require('@vercel/build-utils');
|
||||
const { FileBlob, Lambda } = require('@vercel/build-utils');
|
||||
|
||||
exports.build = async () => {
|
||||
const file = new FileBlob({
|
||||
data: Buffer.from('file contents')
|
||||
});
|
||||
const output = { file };
|
||||
const lambda = new Lambda({
|
||||
files: {},
|
||||
runtime: 'provided',
|
||||
handler: 'example.js'
|
||||
})
|
||||
const output = {
|
||||
file,
|
||||
'withTrailingSlash/': lambda
|
||||
};
|
||||
return { output };
|
||||
};
|
||||
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "bad-remote-url"
|
||||
}
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/bad-remote-url/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = bababooey
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "existing-connection"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/existing-connection/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "invalid-repo"
|
||||
}
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/invalid-repo/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "multiple-remotes"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
13
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "new-connection"
|
||||
}
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/new-connection/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/no-git-config/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/no-git-config/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/no-git-config/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/no-git-config/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "no-git-config"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "no-remote-url"
|
||||
}
|
||||
7
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/git/config
generated
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/git/connect/no-remote-url/git/config
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "new-connection"
|
||||
}
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/same-repo-connection/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[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/*
|
||||
2
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/.gitignore
generated
vendored
Normal file
2
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!.vercel
|
||||
.vercel
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
10
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/config
generated
vendored
Normal file
10
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/config
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/unlinked/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "connected-repo"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/COMMIT_EDITMSG
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/COMMIT_EDITMSG
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
add hi
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
13
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[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/*
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
generated
vendored
Normal file
Binary file not shown.
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/80/50816205303e5957b2909083c50677930d5b29
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/80/50816205303e5957b2909083c50677930d5b29
generated
vendored
Normal file
Binary file not shown.
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/b2/e4d98c09ed53967b0a8f67c293c04ca4173438
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/b2/e4d98c09ed53967b0a8f67c293c04ca4173438
generated
vendored
Normal file
Binary file not shown.
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/refs/heads/master
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/refs/heads/master
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
8050816205303e5957b2909083c50677930d5b29
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/index.txt
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/index.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hi
|
||||
11
packages/cli/test/fixtures/unit/create-git-meta/git-corrupt/git/config
generated
vendored
Normal file
11
packages/cli/test/fixtures/unit/create-git-meta/git-corrupt/git/config
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
fileMode = false
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
[user]
|
||||
name = TechBug2012
|
||||
email = <>
|
||||
[remote "origin"]
|
||||
url = https://github.com/MatthewStanciu/git-test
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user