mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
23 Commits
@vercel/py
...
@vercel/go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f8d178f7 | ||
|
|
f9a747764c | ||
|
|
27d80f13cd | ||
|
|
8c668c925d | ||
|
|
4b1b33c143 | ||
|
|
a8d4147554 | ||
|
|
09339f494d | ||
|
|
ee4d772ae9 | ||
|
|
61e8103404 | ||
|
|
fb4f477325 | ||
|
|
016bff848e | ||
|
|
183e411f7c | ||
|
|
070e300148 | ||
|
|
cbdf9b4a88 | ||
|
|
ec9b55dc81 | ||
|
|
06829bc21a | ||
|
|
628071f659 | ||
|
|
5a7461dfe3 | ||
|
|
599f8f675c | ||
|
|
0a8bc494fc | ||
|
|
34e008f42e | ||
|
|
037633b3f1 | ||
|
|
1a6f3c0270 |
@@ -5,7 +5,8 @@
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.0.1",
|
||||
"version": "5.0.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
48
packages/build-utils/test/unit.test.ts
vendored
48
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';
|
||||
@@ -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.1.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.2",
|
||||
"@vercel/go": "2.0.6",
|
||||
"@vercel/hydrogen": "0.0.3",
|
||||
"@vercel/next": "3.1.5",
|
||||
"@vercel/node": "2.4.3",
|
||||
"@vercel/python": "3.0.6",
|
||||
"@vercel/redwood": "1.0.7",
|
||||
"@vercel/remix": "1.0.8",
|
||||
"@vercel/ruby": "1.3.14",
|
||||
"@vercel/static-build": "1.0.6",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -96,9 +96,9 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.1.0",
|
||||
"@vercel/client": "12.1.1",
|
||||
"@vercel/frameworks": "1.1.0",
|
||||
"@vercel/fs-detectors": "1.0.1",
|
||||
"@vercel/fs-detectors": "2.0.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
|
||||
@@ -336,8 +336,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
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) {
|
||||
if (typeof build.src !== 'string') continue;
|
||||
|
||||
@@ -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;
|
||||
|
||||
168
packages/cli/src/commands/git/connect.ts
Normal file
168
packages/cli/src/commands/git/connect.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import { Org, Project } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { parseGitConfig, pluckRemoteUrl } from '../../util/create-git-meta';
|
||||
import confirm from '../../util/input/confirm';
|
||||
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 remoteUrl = pluckRemoteUrl(gitConfig);
|
||||
if (!remoteUrl) {
|
||||
output.error(
|
||||
`No remote origin URL 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;
|
||||
}
|
||||
output.log(`Identified Git remote "origin": ${link(remoteUrl)}`);
|
||||
const parsedUrl = parseRepoUrl(remoteUrl);
|
||||
if (!parsedUrl) {
|
||||
output.error(
|
||||
`Failed to parse Git repo data from the following remote URL in your Git config: ${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;
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -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,', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@ 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';
|
||||
import { GitMetadata } from '../types';
|
||||
import { Output } from './output';
|
||||
|
||||
export function isDirty(directory: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
export function isDirty(directory: string, output: Output): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
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()}`
|
||||
)
|
||||
);
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -33,21 +33,31 @@ function getLastCommit(directory: string): Promise<git.Commit> {
|
||||
});
|
||||
}
|
||||
|
||||
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 pluckRemoteUrl(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 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}`);
|
||||
}
|
||||
let gitConfig = await parseGitConfig(configPath, output);
|
||||
if (!gitConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originUrl: string = gitConfig['remote "origin"']?.url;
|
||||
const originUrl = pluckRemoteUrl(gitConfig);
|
||||
if (originUrl) {
|
||||
return originUrl;
|
||||
}
|
||||
@@ -64,10 +74,19 @@ export async function createGitMeta(
|
||||
return;
|
||||
}
|
||||
const [commit, dirty] = await Promise.all([
|
||||
getLastCommit(directory),
|
||||
isDirty(directory),
|
||||
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,
|
||||
@@ -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,
|
||||
|
||||
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/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.
|
||||
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/*
|
||||
19
packages/cli/test/helpers/parse-table.ts
Normal file
19
packages/cli/test/helpers/parse-table.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export function pluckIdentifiersFromDeploymentList(output: string): {
|
||||
project: string | undefined;
|
||||
org: string | undefined;
|
||||
} {
|
||||
const project = output.match(/(?<=Deployments for )(.*)(?= under)/);
|
||||
const org = output.match(/(?<=under )(.*)(?= \[)/);
|
||||
|
||||
return {
|
||||
project: project?.[0],
|
||||
org: org?.[0],
|
||||
};
|
||||
}
|
||||
|
||||
export function parseSpacedTableRow(output: string): string[] {
|
||||
return output
|
||||
.trim()
|
||||
.replace(/ {1} +/g, ',')
|
||||
.split(',');
|
||||
}
|
||||
25
packages/cli/test/helpers/read-output-stream.ts
Normal file
25
packages/cli/test/helpers/read-output-stream.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { MockClient } from '../mocks/client';
|
||||
|
||||
export function readOutputStream(
|
||||
client: MockClient,
|
||||
length: number = 3
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let output: string = '';
|
||||
let lines = 0;
|
||||
const timeout = setTimeout(() => {
|
||||
reject();
|
||||
}, 3000);
|
||||
|
||||
client.stderr.resume();
|
||||
client.stderr.on('data', chunk => {
|
||||
output += chunk.toString();
|
||||
lines++;
|
||||
if (lines === length) {
|
||||
clearTimeout(timeout);
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
client.stderr.on('error', reject);
|
||||
});
|
||||
}
|
||||
11
packages/cli/test/integration.js
vendored
11
packages/cli/test/integration.js
vendored
@@ -1323,12 +1323,7 @@ test('[vc projects] should create a project successfully', async t => {
|
||||
Math.random().toString(36).split('.')[1]
|
||||
}`;
|
||||
|
||||
const vc = execa(binaryPath, [
|
||||
'projects',
|
||||
'add',
|
||||
projectName,
|
||||
...defaultArgs,
|
||||
]);
|
||||
const vc = execa(binaryPath, ['project', 'add', projectName, ...defaultArgs]);
|
||||
|
||||
await waitForPrompt(vc, chunk =>
|
||||
chunk.includes(`Success! Project ${projectName} added`)
|
||||
@@ -1339,7 +1334,7 @@ test('[vc projects] should create a project successfully', async t => {
|
||||
|
||||
// creating the same project again should succeed
|
||||
const vc2 = execa(binaryPath, [
|
||||
'projects',
|
||||
'project',
|
||||
'add',
|
||||
projectName,
|
||||
...defaultArgs,
|
||||
@@ -3517,7 +3512,7 @@ test('`vc --debug project ls` should output the projects listing', async t => {
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
t.true(
|
||||
stdout.includes('> Projects found under'),
|
||||
stderr.includes('> Projects found under'),
|
||||
formatOutput({ stderr, stdout })
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,8 +25,6 @@ type GetMatcherType<TP, TResult> = TP extends PromiseFunction
|
||||
? (...args: Tail<Parameters<TP>>) => TResult
|
||||
: TP;
|
||||
|
||||
//type T = GetMatcherType<typeof matchers['toOutput'], void>;
|
||||
|
||||
type GetMatchersType<TMatchers, TResult> = {
|
||||
[P in keyof TMatchers]: GetMatcherType<TMatchers[P], TResult>;
|
||||
};
|
||||
|
||||
@@ -157,6 +157,64 @@ export function useProject(project: Partial<Project> = defaultProject) {
|
||||
|
||||
res.json({ envs });
|
||||
});
|
||||
client.scenario.post(`/v4/projects/${project.id}/link`, (req, res) => {
|
||||
const { type, repo, org } = req.body;
|
||||
if (
|
||||
(type === 'github' || type === 'gitlab' || type === 'bitbucket') &&
|
||||
(repo === 'user/repo' || repo === 'user2/repo2')
|
||||
) {
|
||||
project.link = {
|
||||
type,
|
||||
repo,
|
||||
repoId: 1010,
|
||||
org,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
res.json(project);
|
||||
} else {
|
||||
if (type === 'github') {
|
||||
res.status(400).json({
|
||||
message: `To link a GitHub repository, you need to install the GitHub integration first. (400)\nInstall GitHub App: https://github.com/apps/vercel`,
|
||||
meta: {
|
||||
action: 'Install GitHub App',
|
||||
link: 'https://github.com/apps/vercel',
|
||||
repo,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
code: 'repo_not_found',
|
||||
message: `The repository "${repo}" couldn't be found in your linked ${formatProvider(
|
||||
type
|
||||
)} account.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
client.scenario.delete(`/v4/projects/${project.id}/link`, (_req, res) => {
|
||||
if (project.link) {
|
||||
project.link = undefined;
|
||||
}
|
||||
res.json(project);
|
||||
});
|
||||
client.scenario.get(`/v4/projects`, (req, res) => {
|
||||
res.json({
|
||||
projects: [defaultProject],
|
||||
pagination: null,
|
||||
});
|
||||
});
|
||||
client.scenario.post(`/projects`, (req, res) => {
|
||||
const { name } = req.body;
|
||||
if (name === project.name) {
|
||||
res.json(project);
|
||||
}
|
||||
});
|
||||
client.scenario.delete(`/:version/projects/${project.id}`, (_req, res) => {
|
||||
res.json({});
|
||||
});
|
||||
|
||||
return { project, envs };
|
||||
}
|
||||
|
||||
380
packages/cli/test/unit/commands/git.test.ts
Normal file
380
packages/cli/test/unit/commands/git.test.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
import { client } from '../../mocks/client';
|
||||
import git from '../../../src/commands/git';
|
||||
import { Project } from '../../../src/types';
|
||||
|
||||
describe('git', () => {
|
||||
describe('connect', () => {
|
||||
const originalCwd = process.cwd();
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/commands/git/connect', name);
|
||||
|
||||
it('connects an unlinked project', async () => {
|
||||
const cwd = fixture('unlinked');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'unlinked',
|
||||
name: 'unlinked',
|
||||
});
|
||||
client.setArgv('projects', 'connect');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Which scope should contain your project?'
|
||||
);
|
||||
client.stdin.write('\r');
|
||||
|
||||
await expect(client.stderr).toOutput('Found project');
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo.git`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user/repo!'
|
||||
);
|
||||
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const project: Project = await client.fetch(`/v8/projects/unlinked`);
|
||||
expect(project.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user/repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should fail when there is no git config', async () => {
|
||||
const cwd = fixture('no-git-config');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'no-git-config',
|
||||
name: 'no-git-config',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! No local git repo found. Run \`git clone <url>\` to clone a remote Git repository first.\n`
|
||||
);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should fail when there is no remote url', async () => {
|
||||
const cwd = fixture('no-remote-url');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'no-remote-url',
|
||||
name: 'no-remote-url',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! No remote origin URL found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.`
|
||||
);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should fail when the remote url is bad', async () => {
|
||||
const cwd = fixture('bad-remote-url');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'bad-remote-url',
|
||||
name: 'bad-remote-url',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": bababooey`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! Failed to parse Git repo data from the following remote URL in your Git config: bababooey\n`
|
||||
);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should connect a repo to a project that is not already connected', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Connected GitHub repository user/repo!\n`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const project: Project = await client.fetch(
|
||||
`/v8/projects/new-connection`
|
||||
);
|
||||
expect(project.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user/repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should replace an old connection with a new one', async () => {
|
||||
const cwd = fixture('existing-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
id: 'existing-connection',
|
||||
name: 'existing-connection',
|
||||
});
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
repo: 'repo',
|
||||
org: 'user',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user2/repo2`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Connected GitHub repository user2/repo2!\n`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/existing-connection`
|
||||
);
|
||||
expect(newProjectData.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user2/repo2',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should exit when an already-connected repo is connected', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
repo: 'repo',
|
||||
org: 'user',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> user/repo is already connected to your project.\n`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(1);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should fail when it cannot find the repository', async () => {
|
||||
const cwd = fixture('invalid-repo');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'invalid-repo',
|
||||
name: 'invalid-repo',
|
||||
});
|
||||
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/laksfj/asdgklsadkl`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Failed to link laksfj/asdgklsadkl. Make sure there aren't any typos and that you have access to the repository if it's private.`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(1);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('disconnect', () => {
|
||||
const originalCwd = process.cwd();
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/commands/git/connect', name);
|
||||
|
||||
it('should disconnect a repository', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
repo: 'repo',
|
||||
org: 'user',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
client.setArgv('git', 'disconnect');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Are you sure you want to disconnect user/repo from your project?`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
await expect(client.stderr).toOutput('Disconnected user/repo.');
|
||||
|
||||
const newProjectData: Project = await client.fetch(
|
||||
`/v8/projects/new-connection`
|
||||
);
|
||||
expect(newProjectData.link).toBeUndefined();
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(0);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should fail if there is no repository to disconnect', async () => {
|
||||
const cwd = fixture('new-connection');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'disconnect');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'No Git repository connected. Run `vercel project connect` to connect one.'
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(1);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,15 @@
|
||||
import { client, MockClient } from '../../mocks/client';
|
||||
import { client } from '../../mocks/client';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import list, { stateString } from '../../../src/commands/list';
|
||||
import { join } from 'path';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
import { useDeployment } from '../../mocks/deployment';
|
||||
import { readOutputStream } from '../../helpers/read-output-stream';
|
||||
import {
|
||||
parseSpacedTableRow,
|
||||
pluckIdentifiersFromDeploymentList,
|
||||
} from '../../helpers/parse-table';
|
||||
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/commands/list', name);
|
||||
@@ -32,9 +37,9 @@ describe('list', () => {
|
||||
|
||||
const output = await readOutputStream(client);
|
||||
|
||||
const { org } = getDataFromIntro(output.split('\n')[0]);
|
||||
const header: string[] = parseTable(output.split('\n')[2]);
|
||||
const data: string[] = parseTable(output.split('\n')[3]);
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.splice(2, 1);
|
||||
|
||||
expect(org).toEqual(team[0].slug);
|
||||
@@ -74,9 +79,9 @@ describe('list', () => {
|
||||
|
||||
const output = await readOutputStream(client);
|
||||
|
||||
const { org } = getDataFromIntro(output.split('\n')[0]);
|
||||
const header: string[] = parseTable(output.split('\n')[2]);
|
||||
const data: string[] = parseTable(output.split('\n')[3]);
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.splice(2, 1);
|
||||
|
||||
expect(org).toEqual(teamSlug);
|
||||
@@ -98,42 +103,3 @@ describe('list', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getDataFromIntro(output: string): {
|
||||
project: string | undefined;
|
||||
org: string | undefined;
|
||||
} {
|
||||
const project = output.match(/(?<=Deployments for )(.*)(?= under)/);
|
||||
const org = output.match(/(?<=under )(.*)(?= \[)/);
|
||||
|
||||
return {
|
||||
project: project?.[0],
|
||||
org: org?.[0],
|
||||
};
|
||||
}
|
||||
|
||||
function parseTable(output: string): string[] {
|
||||
return output
|
||||
.trim()
|
||||
.replace(/ {3} +/g, ',')
|
||||
.split(',');
|
||||
}
|
||||
|
||||
function readOutputStream(client: MockClient): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
const timeout = setTimeout(() => {
|
||||
reject();
|
||||
}, 3000);
|
||||
|
||||
client.stderr.resume();
|
||||
client.stderr.on('data', chunk => {
|
||||
chunks.push(chunk);
|
||||
if (chunks.length === 3) {
|
||||
clearTimeout(timeout);
|
||||
resolve(chunks.toString().replace(/,/g, ''));
|
||||
}
|
||||
});
|
||||
client.stderr.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
97
packages/cli/test/unit/commands/project.test.ts
Normal file
97
packages/cli/test/unit/commands/project.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import projects from '../../../src/commands/project';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
import { client } from '../../mocks/client';
|
||||
import { Project } from '../../../src/types';
|
||||
import { readOutputStream } from '../../helpers/read-output-stream';
|
||||
import {
|
||||
pluckIdentifiersFromDeploymentList,
|
||||
parseSpacedTableRow,
|
||||
} from '../../helpers/parse-table';
|
||||
|
||||
describe('project', () => {
|
||||
describe('list', () => {
|
||||
it('should list deployments under a user', async () => {
|
||||
const user = useUser();
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
});
|
||||
|
||||
client.setArgv('project', 'ls');
|
||||
await projects(client);
|
||||
|
||||
const output = await readOutputStream(client, 2);
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.pop();
|
||||
|
||||
expect(org).toEqual(user.username);
|
||||
expect(header).toEqual(['name', 'updated']);
|
||||
expect(data).toEqual([project.project.name]);
|
||||
});
|
||||
it('should list deployments for a team', async () => {
|
||||
useUser();
|
||||
const team = useTeams('team_dummy');
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
});
|
||||
|
||||
client.config.currentTeam = team[0].id;
|
||||
client.setArgv('project', 'ls');
|
||||
await projects(client);
|
||||
|
||||
const output = await readOutputStream(client, 2);
|
||||
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
|
||||
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
|
||||
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
|
||||
data.pop();
|
||||
|
||||
expect(org).toEqual(team[0].slug);
|
||||
expect(header).toEqual(['name', 'updated']);
|
||||
expect(data).toEqual([project.project.name]);
|
||||
});
|
||||
});
|
||||
describe('add', () => {
|
||||
it('should add a project', async () => {
|
||||
const user = useUser();
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'test-project',
|
||||
name: 'test-project',
|
||||
});
|
||||
|
||||
client.setArgv('project', 'add', 'test-project');
|
||||
await projects(client);
|
||||
|
||||
const project: Project = await client.fetch(`/v8/projects/test-project`);
|
||||
expect(project).toBeDefined();
|
||||
|
||||
expect(client.stderr).toOutput(
|
||||
`Success! Project test-project added (${user.username})`
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('rm', () => {
|
||||
it('should remove a project', async () => {
|
||||
useUser();
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'test-project',
|
||||
name: 'test-project',
|
||||
});
|
||||
|
||||
client.setArgv('project', 'rm', 'test-project');
|
||||
const projectsPromise = projects(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`The project test-project will be removed permanently.`
|
||||
);
|
||||
client.stdin.write('y\n');
|
||||
|
||||
const exitCode = await projectsPromise;
|
||||
expect(exitCode).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -101,7 +101,9 @@ describe('pull', () => {
|
||||
Object {
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "vercel-pull-next",
|
||||
"settings": Object {},
|
||||
"settings": Object {
|
||||
"createdAt": 1555413045188,
|
||||
},
|
||||
}
|
||||
`);
|
||||
} finally {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
||||
import {
|
||||
createGitMeta,
|
||||
getRemoteUrl,
|
||||
isDirty,
|
||||
} from '../../../../src/util/deploy/create-git-meta';
|
||||
} from '../../../../src/util/create-git-meta';
|
||||
import { client } from '../../../mocks/client';
|
||||
import { parseRepoUrl } from '../../../../src/util/projects/connect-git-provider';
|
||||
import { readOutputStream } from '../../../helpers/read-output-stream';
|
||||
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../../fixtures/unit/create-git-meta', name);
|
||||
@@ -26,6 +29,97 @@ describe('getRemoteUrl', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseRepoUrl', () => {
|
||||
it('should be null when a url does not match the regex', () => {
|
||||
const parsedUrl = parseRepoUrl('https://examplecom/foo');
|
||||
expect(parsedUrl).toBeNull();
|
||||
});
|
||||
it('should be null when a url does not contain org and repo data', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/borked');
|
||||
expect(parsedUrl).toBeNull();
|
||||
});
|
||||
it('should parse url with a period in the repo name', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/next.js');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('next.js');
|
||||
});
|
||||
it('should parse url that ends with .git', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/next.js.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('next.js');
|
||||
});
|
||||
it('should parse github https url', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github https url without the .git suffix', () => {
|
||||
const parsedUrl = parseRepoUrl('https://github.com/vercel/vercel');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github git url', () => {
|
||||
const parsedUrl = parseRepoUrl('git://github.com/vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
});
|
||||
it('should parse github ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl('git@github.com:vercel/vercel.git');
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('github');
|
||||
expect(parsedUrl?.org).toEqual('vercel');
|
||||
expect(parsedUrl?.repo).toEqual('vercel');
|
||||
});
|
||||
|
||||
it('should parse gitlab https url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
'https://gitlab.com/gitlab-examples/knative-kotlin-app.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('gitlab');
|
||||
expect(parsedUrl?.org).toEqual('gitlab-examples');
|
||||
expect(parsedUrl?.repo).toEqual('knative-kotlin-app');
|
||||
});
|
||||
it('should parse gitlab ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
'git@gitlab.com:gitlab-examples/knative-kotlin-app.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('gitlab');
|
||||
expect(parsedUrl?.org).toEqual('gitlab-examples');
|
||||
expect(parsedUrl?.repo).toEqual('knative-kotlin-app');
|
||||
});
|
||||
|
||||
it('should parse bitbucket https url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
'https://bitbucket.org/atlassianlabs/maven-project-example.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('bitbucket');
|
||||
expect(parsedUrl?.org).toEqual('atlassianlabs');
|
||||
expect(parsedUrl?.repo).toEqual('maven-project-example');
|
||||
});
|
||||
it('should parse bitbucket ssh url', () => {
|
||||
const parsedUrl = parseRepoUrl(
|
||||
'git@bitbucket.org:atlassianlabs/maven-project-example.git'
|
||||
);
|
||||
expect(parsedUrl).toBeDefined();
|
||||
expect(parsedUrl?.provider).toEqual('bitbucket');
|
||||
expect(parsedUrl?.org).toEqual('atlassianlabs');
|
||||
expect(parsedUrl?.repo).toEqual('maven-project-example');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGitMeta', () => {
|
||||
it('is undefined when it does not receive a remote url', async () => {
|
||||
const directory = fixture('no-origin');
|
||||
@@ -41,7 +135,7 @@ describe('createGitMeta', () => {
|
||||
const directory = fixture('dirty');
|
||||
try {
|
||||
await fs.rename(join(directory, 'git'), join(directory, '.git'));
|
||||
const dirty = await isDirty(directory);
|
||||
const dirty = await isDirty(directory, client.output);
|
||||
expect(dirty).toBeTruthy();
|
||||
} finally {
|
||||
await fs.rename(join(directory, '.git'), join(directory, 'git'));
|
||||
@@ -51,7 +145,7 @@ describe('createGitMeta', () => {
|
||||
const directory = fixture('not-dirty');
|
||||
try {
|
||||
await fs.rename(join(directory, 'git'), join(directory, '.git'));
|
||||
const dirty = await isDirty(directory);
|
||||
const dirty = await isDirty(directory, client.output);
|
||||
expect(dirty).toBeFalsy();
|
||||
} finally {
|
||||
await fs.rename(join(directory, '.git'), join(directory, 'git'));
|
||||
@@ -125,4 +219,29 @@ describe('createGitMeta', () => {
|
||||
await fs.rename(join(directory, '.git'), join(directory, 'git'));
|
||||
}
|
||||
});
|
||||
it('fails when `.git` is corrupt', async () => {
|
||||
const directory = fixture('git-corrupt');
|
||||
const tmpDir = join(os.tmpdir(), 'git-corrupt');
|
||||
try {
|
||||
// Copy the fixture into a temp dir so that we don't pick
|
||||
// up Git information from the `vercel/vercel` repo itself
|
||||
await fs.copy(directory, tmpDir);
|
||||
await fs.rename(join(tmpDir, 'git'), join(tmpDir, '.git'));
|
||||
|
||||
client.output.debugEnabled = true;
|
||||
const data = await createGitMeta(tmpDir, client.output);
|
||||
|
||||
const output = await readOutputStream(client, 2);
|
||||
|
||||
expect(output).toContain(
|
||||
`Failed to get last commit. The directory is likely not a Git repo, there are no latest commits, or it is corrupted.`
|
||||
);
|
||||
expect(output).toContain(
|
||||
`Failed to determine if Git repo has been modified:`
|
||||
);
|
||||
expect(data).toBeUndefined();
|
||||
} finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "12.1.0",
|
||||
"version": "12.1.1",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -42,7 +42,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/routing-utils": "1.13.5",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/fs-detectors",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Vercel filesystem detectors",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -13,8 +13,6 @@ interface Metadata {
|
||||
hasMiddleware: boolean;
|
||||
}
|
||||
|
||||
const enableFileSystemApiFrameworks = new Set(['solidstart']);
|
||||
|
||||
/**
|
||||
* If the Deployment can be built with the new File System API,
|
||||
* return the new Builder. Otherwise an "Exclusion Condition"
|
||||
@@ -61,11 +59,7 @@ export async function detectFileSystemAPI({
|
||||
hasMiddleware,
|
||||
};
|
||||
|
||||
const isEnabled =
|
||||
enableFlag ||
|
||||
hasMiddleware ||
|
||||
hasDotOutput ||
|
||||
enableFileSystemApiFrameworks.has(framework);
|
||||
const isEnabled = enableFlag || hasMiddleware || hasDotOutput;
|
||||
if (!isEnabled) {
|
||||
return { metadata, fsApiBuilder: null, reason: 'Flag not enabled.' };
|
||||
}
|
||||
|
||||
@@ -80,11 +80,13 @@ export async function detectFramework({
|
||||
fs,
|
||||
frameworkList,
|
||||
}: DetectFrameworkOptions): Promise<string | null> {
|
||||
for (const framework of frameworkList) {
|
||||
if (await matches(fs, framework)) {
|
||||
return framework.slug;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
const result = await Promise.all(
|
||||
frameworkList.map(async frameworkMatch => {
|
||||
if (await matches(fs, frameworkMatch)) {
|
||||
return frameworkMatch.slug;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
return result.find(res => res !== null) ?? null;
|
||||
}
|
||||
|
||||
@@ -252,6 +252,19 @@ describe('DetectorFilesystem', () => {
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
|
||||
});
|
||||
|
||||
it('Detect frameworks based on ascending order in framework list', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
gatsby: '4.18.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.6",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
@@ -25,7 +25,7 @@
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/node-fetch": "^2.3.0",
|
||||
"@types/tar": "^4.0.0",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"async-retry": "1.3.1",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/hydrogen",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.5",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/text-table": "0.2.1",
|
||||
"@types/webpack-sources": "3.2.0",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/nft": "0.20.1",
|
||||
"@vercel/routing-utils": "1.13.5",
|
||||
"async-sema": "3.0.1",
|
||||
|
||||
@@ -399,6 +399,7 @@ export const build: BuildV2 = async ({
|
||||
const env: typeof process.env = { ...spawnOpts.env };
|
||||
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
|
||||
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
||||
env.NEXT_EDGE_RUNTIME_PROVIDER = 'vercel';
|
||||
|
||||
if (target) {
|
||||
// Since version v10.0.8-canary.15 of Next.js the NEXT_PRIVATE_TARGET env
|
||||
|
||||
@@ -2148,6 +2148,7 @@ interface EdgeFunctionInfo {
|
||||
page: string;
|
||||
regexp: string;
|
||||
wasm?: { filePath: string; name: string }[];
|
||||
assets?: { filePath: string; name: string }[];
|
||||
}
|
||||
|
||||
export async function getMiddlewareBundle({
|
||||
@@ -2234,6 +2235,23 @@ export async function getMiddlewareBundle({
|
||||
{}
|
||||
);
|
||||
|
||||
const assetFiles = (edgeFunction.assets ?? []).reduce(
|
||||
(acc: Files, { filePath, name }) => {
|
||||
const fullFilePath = path.join(
|
||||
entryPath,
|
||||
outputDirectory,
|
||||
filePath
|
||||
);
|
||||
acc[`assets/${name}`] = new FileFsRef({
|
||||
mode: 0o644,
|
||||
contentType: 'application/octet-stream',
|
||||
fsPath: fullFilePath,
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return new EdgeFunction({
|
||||
deploymentTarget: 'v8-worker',
|
||||
name: edgeFunction.name,
|
||||
@@ -2251,9 +2269,16 @@ export async function getMiddlewareBundle({
|
||||
}),
|
||||
}),
|
||||
...wasmFiles,
|
||||
...assetFiles,
|
||||
},
|
||||
entrypoint: 'index.js',
|
||||
envVarsInUse: edgeFunction.env,
|
||||
assets: (edgeFunction.assets ?? []).map(({ name }) => {
|
||||
return {
|
||||
name,
|
||||
path: `assets/${name}`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
})(),
|
||||
routeSrc: getRouteSrc(edgeFunction, routesManifest),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/node-bridge": "3.0.0",
|
||||
"@vercel/static-config": "2.0.1",
|
||||
"edge-runtime": "1.0.1",
|
||||
|
||||
@@ -193,7 +193,7 @@ async function compileUserCode(entrypoint: string) {
|
||||
|
||||
let edgeHandler = module.exports.default;
|
||||
if (!edgeHandler) {
|
||||
throw new Error('No default export was found. Add a default export to handle requests.');
|
||||
throw new Error('No default export was found. Add a default export to handle requests. Learn more: https://vercel.link/creating-edge-middleware');
|
||||
}
|
||||
|
||||
let response = await edgeHandler(event.request, event);
|
||||
@@ -305,7 +305,7 @@ function parseRuntime(
|
||||
throw new Error(
|
||||
`Invalid function runtime "${runtime}" for "${entrypoint}". Valid runtimes are: ${JSON.stringify(
|
||||
validRuntimes
|
||||
)}`
|
||||
)}. Learn more: https://vercel.link/creating-edge-functions`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/python",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.6",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||
@@ -23,7 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/execa": "^0.9.0",
|
||||
"@types/jest": "27.4.1",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "^1.0.0",
|
||||
"typescript": "4.3.4"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/redwood",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -28,6 +28,6 @@
|
||||
"@types/aws-lambda": "8.10.19",
|
||||
"@types/node": "*",
|
||||
"@types/semver": "6.0.0",
|
||||
"@vercel/build-utils": "5.0.1"
|
||||
"@vercel/build-utils": "5.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/remix",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -26,7 +26,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import {
|
||||
debug,
|
||||
download,
|
||||
@@ -187,6 +187,15 @@ export const build: BuildV2 = async ({
|
||||
// Explicit directory path the server output will be
|
||||
serverBuildPath = join(remixConfig.serverBuildDirectory, 'index.js');
|
||||
}
|
||||
|
||||
// Also check for whether were in a monorepo.
|
||||
// If we are, prepend the app root directory from config onto the build path.
|
||||
// e.g. `/apps/my-remix-app/api/index.js`
|
||||
const isMonorepo = repoRootPath && repoRootPath !== workPath;
|
||||
if (isMonorepo) {
|
||||
const rootDirectory = relative(repoRootPath, workPath);
|
||||
serverBuildPath = join(rootDirectory, serverBuildPath);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Ignore error if `remix.config.js` does not exist
|
||||
if (err.code !== 'MODULE_NOT_FOUND') throw err;
|
||||
@@ -196,6 +205,7 @@ export const build: BuildV2 = async ({
|
||||
glob('**', join(entrypointFsDirname, 'public')),
|
||||
createRenderFunction(
|
||||
entrypointFsDirname,
|
||||
repoRootPath,
|
||||
serverBuildPath,
|
||||
needsHandler,
|
||||
nodeVersion
|
||||
@@ -230,6 +240,7 @@ function hasScript(scriptName: string, pkg: PackageJson | null) {
|
||||
}
|
||||
|
||||
async function createRenderFunction(
|
||||
entrypointDir: string,
|
||||
rootDir: string,
|
||||
serverBuildPath: string,
|
||||
needsHandler: boolean,
|
||||
@@ -250,6 +261,7 @@ async function createRenderFunction(
|
||||
// Trace the handler with `@vercel/nft`
|
||||
const trace = await nodeFileTrace([handlerPath], {
|
||||
base: rootDir,
|
||||
processCwd: entrypointDir,
|
||||
});
|
||||
|
||||
for (const warning of trace.warnings) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@vercel/ruby",
|
||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.14",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
||||
@@ -23,7 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "8.0.0",
|
||||
"@types/semver": "6.0.0",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "2.0.4",
|
||||
"fs-extra": "^7.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/static-build",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/build-step",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@types/promise-timeout": "1.3.0",
|
||||
"@vercel/build-utils": "5.0.1",
|
||||
"@vercel/build-utils": "5.0.2",
|
||||
"@vercel/frameworks": "1.1.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/routing-utils": "1.13.5",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"build": {
|
||||
"env": {
|
||||
"ENABLE_FILE_SYSTEM_API": "1"
|
||||
"ENABLE_VC_BUILD": "1"
|
||||
}
|
||||
},
|
||||
"github": {
|
||||
|
||||
Reference in New Issue
Block a user