Compare commits

...

1 Commits

Author SHA1 Message Date
Nathan Rajlich
3381c1018a [cli] WIP Show build logs during vc deploy 2022-12-19 09:21:46 -08:00
6 changed files with 106 additions and 7 deletions

View File

@@ -102,6 +102,7 @@
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2",
"abort-controller": "3.0.0",
"ajv": "6.12.2",
"alpha-sort": "2.0.1",
"ansi-escapes": "3.0.0",
@@ -175,6 +176,8 @@
"utility-types": "2.1.0",
"write-json-file": "2.2.0",
"xdg-app-paths": "5.1.0",
"xterm-addon-serialize": "0.8.0",
"xterm-headless": "5.0.0",
"yauzl-promise": "2.1.3"
},
"jest": {

View File

@@ -126,7 +126,7 @@ export default class Client extends EventEmitter implements Stdio {
}, fetch(url, { ...opts, headers, body }));
}
fetch(url: string, opts: { json: false }): Promise<Response>;
fetch(url: string, opts: FetchOptions & { json: false }): Promise<Response>;
fetch<T>(url: string, opts?: FetchOptions): Promise<T>;
fetch(url: string, opts: FetchOptions = {}) {
return this.retry(async bail => {

View File

@@ -6,6 +6,9 @@ import {
DeploymentOptions,
VercelClientOptions,
} from '@vercel/client';
import { Terminal } from 'xterm-headless';
import { SerializeAddon } from 'xterm-addon-serialize';
import { AbortController } from 'abort-controller';
import { Output } from '../output';
import { progress } from '../output/progress';
import Now from '../../util';
@@ -13,6 +16,7 @@ import { Org } from '../../types';
import ua from '../ua';
import { linkFolderToProject } from '../projects/link';
import { prependEmoji, emoji } from '../emoji';
import { createLogsIterator } from '../logs/iterator';
function printInspectUrl(
output: Output,
@@ -100,6 +104,8 @@ export default async function processDeployment({
// the deployment is done
const indications = [];
const buildLogsAbortController = new AbortController();
try {
for await (const event of createDeployment(clientOptions, requestBody)) {
if (['tip', 'notice', 'warning'].includes(event.type)) {
@@ -180,14 +186,49 @@ export default async function processDeployment({
process.stdout.write(`https://${event.payload.url}`);
}
output.spinner(
event.payload.readyState === 'QUEUED' ? 'Queued' : 'Building',
0
);
if (event.payload.readyState === 'QUEUED') {
output.spinner('Queued', 0);
}
}
const term = new Terminal({
allowProposedApi: true,
cols: 120,
rows: 30,
});
const serializeAddon = new SerializeAddon();
term.loadAddon(serializeAddon);
if (event.type === 'building') {
output.spinner('Building', 0);
// Pipe build logs until the deployment is ready or running checks
(async (abort: AbortController) => {
const it = createLogsIterator(now._client, event.payload.id, abort);
//let isFirst = true;
for await (const events of it) {
for (const event of events) {
if (event.type === 'stdout' || event.type === 'stderr') {
output.stopSpinner();
process.stderr.write(event.text);
//await new Promise<void>(r => term.write(event.text.replace(/\n/g, '\r\n'), () => {
// const serialized = serializeAddon.serialize({ scrollback: 0 }).split('\n');
// if (!isFirst) {
// now._client.stderr.write(`\r\u001b[11A\u001b[J`);
// }
// isFirst = false;
// for (let i = 0; i < 10; i++) {
// const line = serialized[serialized.length - 10 + i] || '';
// now._client.stderr.write(line);
// if (i !== 9) {
// now._client.stderr.write('\n');
// }
// }
// r();
//}));
}
}
}
})(buildLogsAbortController);
}
if (event.type === 'canceled') {
@@ -203,10 +244,12 @@ export default async function processDeployment({
? event.payload.checksState === 'completed'
: true)
) {
buildLogsAbortController.abort();
output.spinner('Completing', 0);
}
if (event.type === 'checks-running') {
buildLogsAbortController.abort();
output.spinner('Running Checks', 0);
}

View File

@@ -57,7 +57,7 @@ async function printEvents(
if (findOpts.since) query.set('since', String(findOpts.since));
if (findOpts.until) query.set('until', String(findOpts.until));
const eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
const eventsUrl = `/v1/deployments/${deploymentIdOrURL}/events?${query}`;
const eventsRes = await client.fetch(eventsUrl, { json: false });
if (eventsRes.ok) {

View File

@@ -0,0 +1,31 @@
import { URLSearchParams } from 'url';
import { on } from 'events';
import jsonlines from 'jsonlines';
import type Client from '../client';
import type { AbortController } from 'abort-controller';
export async function* createLogsIterator(
client: Client,
deploymentId: string,
abortController?: AbortController
) {
const query = new URLSearchParams({
direction: 'forward',
follow: '1',
format: 'lines',
});
const eventsUrl = `/v1/deployments/${deploymentId}/events?${query}`;
const eventsRes = await client.fetch(eventsUrl, {
json: false,
signal: abortController?.signal,
});
if (!eventsRes.ok) {
throw new Error(await eventsRes.text());
}
if (abortController?.signal.aborted) return;
const stream = eventsRes.body.pipe(jsonlines.parse());
abortController?.signal.addEventListener('abort', () => {
stream.destroy();
});
yield* on(stream, 'data');
}

View File

@@ -3498,6 +3498,13 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abort-controller@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -6238,6 +6245,11 @@ etag@1.8.1, etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
@@ -13877,6 +13889,16 @@ xtend@^4.0.0, xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
xterm-addon-serialize@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0.tgz#715b510b91cc8e0d32844ca2a7de4d0fb5d49d9d"
integrity sha512-8N4RBaxM4TwlZRFXYz+xJwHUeFRXscRIM9O3wq26T0DrG7lM9JprSq1F4IGO1I5Et/CqXjiBFD50KwqkRKF0/w==
xterm-headless@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.0.0.tgz#6426e85aec24f1bdfd322fe4e5159e0113f059d4"
integrity sha512-d+3C883Tz5zNCcapJSUpZVO8lplKhjJZtvuL4oIMTE74BX/3UsK7zUQgp5c6KS5Vv4FjFSb3XQA+n2Cx9Znq+w==
"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"