2023-01-10 15:49:41 +00:00
|
|
|
// Copyright 2022 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2023-03-28 15:15:22 +00:00
|
|
|
import assert from 'node:assert';
|
2023-01-10 15:49:41 +00:00
|
|
|
import * as globbyModule from 'globby';
|
|
|
|
import minimist from 'minimist';
|
|
|
|
import nodeFetch from 'node-fetch';
|
|
|
|
import { createInterface } from 'node:readline';
|
2023-03-28 15:15:22 +00:00
|
|
|
import { $, within, ProcessOutput } from './core.js';
|
2023-01-10 15:49:41 +00:00
|
|
|
import { isString, parseDuration } from './util.js';
|
2023-03-28 15:15:22 +00:00
|
|
|
import chalk from 'chalk';
|
2023-01-10 15:49:41 +00:00
|
|
|
export { default as chalk } from 'chalk';
|
|
|
|
export { default as fs } from 'fs-extra';
|
|
|
|
export { default as which } from 'which';
|
|
|
|
export { default as YAML } from 'yaml';
|
|
|
|
export { default as path } from 'node:path';
|
|
|
|
export { default as os } from 'node:os';
|
2023-03-28 15:15:22 +00:00
|
|
|
export { ssh } from 'webpod';
|
2023-01-10 15:49:41 +00:00
|
|
|
export let argv = minimist(process.argv.slice(2));
|
|
|
|
export function updateArgv(args) {
|
|
|
|
argv = minimist(args);
|
|
|
|
global.argv = argv;
|
|
|
|
}
|
|
|
|
export const globby = Object.assign(function globby(patterns, options) {
|
|
|
|
return globbyModule.globby(patterns, options);
|
|
|
|
}, globbyModule);
|
|
|
|
export const glob = globby;
|
|
|
|
export function sleep(duration) {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
setTimeout(resolve, parseDuration(duration));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
export async function fetch(url, init) {
|
|
|
|
$.log({ kind: 'fetch', url, init });
|
|
|
|
return nodeFetch(url, init);
|
|
|
|
}
|
|
|
|
export function echo(pieces, ...args) {
|
|
|
|
let msg;
|
|
|
|
const lastIdx = pieces.length - 1;
|
|
|
|
if (Array.isArray(pieces) &&
|
|
|
|
pieces.every(isString) &&
|
|
|
|
lastIdx === args.length) {
|
|
|
|
msg =
|
|
|
|
args.map((a, i) => pieces[i] + stringify(a)).join('') + pieces[lastIdx];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
msg = [pieces, ...args].map(stringify).join(' ');
|
|
|
|
}
|
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
function stringify(arg) {
|
|
|
|
if (arg instanceof ProcessOutput) {
|
|
|
|
return arg.toString().replace(/\n$/, '');
|
|
|
|
}
|
|
|
|
return `${arg}`;
|
|
|
|
}
|
|
|
|
export async function question(query, options) {
|
|
|
|
let completer = undefined;
|
|
|
|
if (options && Array.isArray(options.choices)) {
|
|
|
|
/* c8 ignore next 5 */
|
|
|
|
completer = function completer(line) {
|
|
|
|
const completions = options.choices;
|
|
|
|
const hits = completions.filter((c) => c.startsWith(line));
|
|
|
|
return [hits.length ? hits : completions, line];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const rl = createInterface({
|
|
|
|
input: process.stdin,
|
|
|
|
output: process.stdout,
|
|
|
|
terminal: true,
|
|
|
|
completer,
|
|
|
|
});
|
|
|
|
return new Promise((resolve) => rl.question(query ?? '', (answer) => {
|
|
|
|
rl.close();
|
|
|
|
resolve(answer);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
export async function stdin() {
|
|
|
|
let buf = '';
|
|
|
|
process.stdin.setEncoding('utf8');
|
|
|
|
for await (const chunk of process.stdin) {
|
|
|
|
buf += chunk;
|
|
|
|
}
|
|
|
|
return buf;
|
|
|
|
}
|
2023-03-28 15:15:22 +00:00
|
|
|
export async function retry(count, a, b) {
|
|
|
|
const total = count;
|
|
|
|
let callback;
|
|
|
|
let delayStatic = 0;
|
|
|
|
let delayGen;
|
|
|
|
if (typeof a == 'function') {
|
|
|
|
callback = a;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (typeof a == 'object') {
|
|
|
|
delayGen = a;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delayStatic = parseDuration(a);
|
|
|
|
}
|
|
|
|
assert(b);
|
|
|
|
callback = b;
|
|
|
|
}
|
|
|
|
let lastErr;
|
|
|
|
let attempt = 0;
|
|
|
|
while (count-- > 0) {
|
|
|
|
attempt++;
|
|
|
|
try {
|
|
|
|
return await callback();
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
let delay = 0;
|
|
|
|
if (delayStatic > 0)
|
|
|
|
delay = delayStatic;
|
|
|
|
if (delayGen)
|
|
|
|
delay = delayGen.next().value;
|
|
|
|
$.log({
|
|
|
|
kind: 'retry',
|
|
|
|
error: chalk.bgRed.white(' FAIL ') +
|
|
|
|
` Attempt: ${attempt}${total == Infinity ? '' : `/${total}`}` +
|
|
|
|
(delay > 0 ? `; next in ${delay}ms` : ''),
|
|
|
|
});
|
|
|
|
lastErr = err;
|
|
|
|
if (count == 0)
|
|
|
|
break;
|
|
|
|
if (delay)
|
|
|
|
await sleep(delay);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw lastErr;
|
|
|
|
}
|
|
|
|
export function* expBackoff(max = '60s', rand = '100ms') {
|
|
|
|
const maxMs = parseDuration(max);
|
|
|
|
const randMs = parseDuration(rand);
|
|
|
|
let n = 1;
|
|
|
|
while (true) {
|
|
|
|
const ms = Math.floor(Math.random() * randMs);
|
|
|
|
yield Math.min(2 ** n++, maxMs) + ms;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export async function spinner(title, callback) {
|
|
|
|
if (typeof title == 'function') {
|
|
|
|
callback = title;
|
|
|
|
title = '';
|
|
|
|
}
|
|
|
|
let i = 0;
|
|
|
|
const spin = () => process.stderr.write(` ${'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'[i++ % 10]} ${title}\r`);
|
|
|
|
return within(async () => {
|
|
|
|
$.verbose = false;
|
|
|
|
const id = setInterval(spin, 100);
|
|
|
|
let result;
|
|
|
|
try {
|
|
|
|
result = await callback();
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
clearInterval(id);
|
|
|
|
process.stderr.write(' '.repeat(process.stdout.columns - 1) + '\r');
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
}
|