Files
vercel/packages/cli/src/util/deploy/process-deployment.ts
Ethan Arrowood af239b5fa5 [internals] Create @vercel-internals/types (#9608)
Moves the type file out of the cli package and into its own standalone
package. utilizes `@vercel/style-guide` too for typescript config,
eslint, and prettier.
2023-03-07 08:44:25 -07:00

244 lines
6.0 KiB
TypeScript

import bytes from 'bytes';
import chalk from 'chalk';
import {
ArchiveFormat,
createDeployment,
DeploymentOptions,
VercelClientOptions,
} from '@vercel/client';
import { Output } from '../output';
import { progress } from '../output/progress';
import Now from '../../util';
import { Org } from '@vercel-internals/types';
import ua from '../ua';
import { linkFolderToProject } from '../projects/link';
import { prependEmoji, emoji } from '../emoji';
function printInspectUrl(
output: Output,
inspectorUrl: string,
deployStamp: () => string
) {
output.print(
prependEmoji(
`Inspect: ${chalk.bold(inspectorUrl)} ${deployStamp()}`,
emoji('inspect')
) + `\n`
);
}
export default async function processDeployment({
org,
cwd,
projectName,
isSettingUpProject,
archive,
skipAutoDetectionConfirmation,
...args
}: {
now: Now;
output: Output;
paths: string[];
requestBody: DeploymentOptions;
uploadStamp: () => string;
deployStamp: () => string;
quiet: boolean;
force?: boolean;
withCache?: boolean;
org: Org;
prebuilt: boolean;
projectName: string;
isSettingUpProject: boolean;
archive?: ArchiveFormat;
skipAutoDetectionConfirmation?: boolean;
cwd?: string;
rootDirectory?: string;
}) {
let {
now,
output,
paths,
requestBody,
deployStamp,
force,
withCache,
quiet,
prebuilt,
rootDirectory,
} = args;
const { debug } = output;
const { env = {} } = requestBody;
const token = now._token;
if (!token) {
throw new Error('Missing authentication token');
}
const clientOptions: VercelClientOptions = {
teamId: org.type === 'team' ? org.id : undefined,
apiUrl: now._apiUrl,
token,
debug: now._debug,
userAgent: ua,
path: paths[0],
force,
withCache,
prebuilt,
rootDirectory,
skipAutoDetectionConfirmation,
archive,
};
const deployingSpinnerVal = isSettingUpProject
? 'Setting up project'
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`;
output.spinner(deployingSpinnerVal, 0);
// collect indications to show the user once
// the deployment is done
const indications = [];
try {
for await (const event of createDeployment(clientOptions, requestBody)) {
if (['tip', 'notice', 'warning'].includes(event.type)) {
indications.push(event);
}
if (event.type === 'file-count') {
const { total, missing, uploads } = event.payload;
debug(`Total files ${total.size}, ${missing.length} changed`);
const missingSize = missing
.map((sha: string) => total.get(sha).data.length)
.reduce((a: number, b: number) => a + b, 0);
const totalSizeHuman = bytes.format(missingSize, { decimalPlaces: 1 });
// When stderr is not a TTY then we only want to
// print upload progress in 25% increments
let nextStep = 0;
const stepSize = now._client.stderr.isTTY ? 0 : 0.25;
const updateProgress = () => {
const uploadedBytes = uploads.reduce((acc: number, e: any) => {
return acc + e.bytesUploaded;
}, 0);
const bar = progress(uploadedBytes, missingSize);
if (!bar) {
output.spinner(deployingSpinnerVal, 0);
} else {
const uploadedHuman = bytes.format(uploadedBytes, {
decimalPlaces: 1,
fixedDecimals: true,
});
const percent = uploadedBytes / missingSize;
if (percent >= nextStep) {
output.spinner(
`Uploading ${chalk.reset(
`[${bar}] (${uploadedHuman}/${totalSizeHuman})`
)}`,
0
);
nextStep += stepSize;
}
}
};
uploads.forEach((e: any) => e.on('progress', updateProgress));
updateProgress();
}
if (event.type === 'file-uploaded') {
debug(
`Uploaded: ${event.payload.file.names.join(' ')} (${bytes(
event.payload.file.data.length
)})`
);
}
if (event.type === 'created') {
await linkFolderToProject(
output,
cwd || paths[0],
{
orgId: org.id,
projectId: event.payload.projectId,
},
projectName,
org.slug
);
now.url = event.payload.url;
output.stopSpinner();
printInspectUrl(output, event.payload.inspectorUrl, deployStamp);
if (quiet) {
process.stdout.write(`https://${event.payload.url}`);
}
output.spinner(
event.payload.readyState === 'QUEUED' ? 'Queued' : 'Building',
0
);
}
if (event.type === 'building') {
output.spinner('Building', 0);
}
if (event.type === 'canceled') {
output.stopSpinner();
return event.payload;
}
// If `checksState` is present, we can only continue to "Completing" if the checks finished,
// otherwise we might show "Completing" before "Running Checks".
if (
event.type === 'ready' &&
(event.payload.checksState
? event.payload.checksState === 'completed'
: true)
) {
output.spinner('Completing', 0);
}
if (event.type === 'checks-running') {
output.spinner('Running Checks', 0);
}
if (event.type === 'checks-conclusion-failed') {
output.stopSpinner();
return event.payload;
}
// Handle error events
if (event.type === 'error') {
output.stopSpinner();
const error = await now.handleDeploymentError(event.payload, {
env,
});
if (error.code === 'missing_project_settings') {
return error;
}
throw error;
}
// Handle alias-assigned event
if (event.type === 'alias-assigned') {
event.payload.indications = indications;
return event.payload;
}
}
} catch (err) {
output.stopSpinner();
throw err;
}
}