mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-30 02:28:51 +00:00
Convert lib/utils/compose to typescript
Change-type: patch
This commit is contained in:
parent
a80f676804
commit
bd021c0a2d
9
lib/utils/compose-types.d.ts
vendored
9
lib/utils/compose-types.d.ts
vendored
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ImageModel, ReleaseModel } from 'balena-release/build/models';
|
||||
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||
import type { Pack } from 'tar-stream';
|
||||
|
||||
@ -52,7 +53,7 @@ export interface ComposeOpts {
|
||||
inlineLogs?: boolean;
|
||||
multiDockerignore: boolean;
|
||||
noParentCheck: boolean;
|
||||
projectName: string;
|
||||
projectName?: string;
|
||||
projectPath: string;
|
||||
isLocal?: boolean;
|
||||
}
|
||||
@ -79,7 +80,7 @@ export interface ComposeProject {
|
||||
export interface Release {
|
||||
client: ReturnType<typeof import('balena-release').createClient>;
|
||||
release: Pick<
|
||||
import('balena-release/build/models').ReleaseModel,
|
||||
ReleaseModel,
|
||||
| 'id'
|
||||
| 'status'
|
||||
| 'commit'
|
||||
@ -91,7 +92,9 @@ export interface Release {
|
||||
| 'start_timestamp'
|
||||
| 'end_timestamp'
|
||||
>;
|
||||
serviceImages: Partial<import('balena-release/build/models').ImageModel>;
|
||||
serviceImages: Dictionary<
|
||||
Omit<ImageModel, 'created_at' | 'is_a_build_of__service' | '__metadata'>
|
||||
>;
|
||||
}
|
||||
|
||||
interface TarDirectoryOptions {
|
||||
|
@ -15,14 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Renderer } from './compose_ts';
|
||||
import type * as SDK from 'balena-sdk';
|
||||
import type Dockerode = require('dockerode');
|
||||
import * as path from 'path';
|
||||
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeOpts,
|
||||
ComposeProject,
|
||||
Release,
|
||||
TaggedImage,
|
||||
} from './compose-types';
|
||||
import { getChalk } from './lazy';
|
||||
import Logger = require('./logger');
|
||||
import { ProgressCallback } from 'docker-progress';
|
||||
|
||||
/**
|
||||
* @returns Promise<{import('./compose-types').ComposeOpts}>
|
||||
*/
|
||||
export function generateOpts(options) {
|
||||
const { promises: fs } = require('fs');
|
||||
export function generateOpts(options: {
|
||||
source?: string;
|
||||
projectName?: string;
|
||||
nologs: boolean;
|
||||
'noconvert-eol': boolean;
|
||||
dockerfile?: string;
|
||||
'multi-dockerignore': boolean;
|
||||
'noparent-check': boolean;
|
||||
}): Promise<ComposeOpts> {
|
||||
const { promises: fs } = require('fs') as typeof import('fs');
|
||||
return fs.realpath(options.source || '.').then((projectPath) => ({
|
||||
projectName: options.projectName,
|
||||
projectPath,
|
||||
@ -34,24 +52,19 @@ export function generateOpts(options) {
|
||||
}));
|
||||
}
|
||||
|
||||
// Parse the given composition and return a structure with info. Input is:
|
||||
// - composePath: the *absolute* path to the directory containing the compose file
|
||||
// - composeStr: the contents of the compose file, as a string
|
||||
/**
|
||||
* @param {string} composePath
|
||||
* @param {string} composeStr
|
||||
* @param {string | undefined} projectName The --projectName flag (build, deploy)
|
||||
* @param {string | undefined} imageTag The --tag flag (build, deploy)
|
||||
* @returns {import('./compose-types').ComposeProject}
|
||||
/** Parse the given composition and return a structure with info. Input is:
|
||||
* - composePath: the *absolute* path to the directory containing the compose file
|
||||
* - composeStr: the contents of the compose file, as a string
|
||||
*/
|
||||
export function createProject(
|
||||
composePath,
|
||||
composeStr,
|
||||
composePath: string,
|
||||
composeStr: string,
|
||||
projectName = '',
|
||||
imageTag = '',
|
||||
) {
|
||||
const yml = require('js-yaml');
|
||||
const compose = require('resin-compose-parse');
|
||||
): ComposeProject {
|
||||
const yml = require('js-yaml') as typeof import('js-yaml');
|
||||
const compose =
|
||||
require('resin-compose-parse') as typeof import('resin-compose-parse');
|
||||
|
||||
// both methods below may throw.
|
||||
const rawComposition = yml.load(composeStr);
|
||||
@ -67,7 +80,8 @@ export function createProject(
|
||||
descr.image.context != null &&
|
||||
descr.image.tag == null
|
||||
) {
|
||||
const { makeImageName } = require('./compose_ts');
|
||||
const { makeImageName } =
|
||||
require('./compose_ts') as typeof import('./compose_ts');
|
||||
descr.image.tag = makeImageName(projectName, descr.serviceName, imageTag);
|
||||
}
|
||||
return descr;
|
||||
@ -80,30 +94,20 @@ export function createProject(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} apiEndpoint
|
||||
* @param {string} auth
|
||||
* @param {number} userId
|
||||
* @param {number} appId
|
||||
* @param {import('resin-compose-parse').Composition} composition
|
||||
* @param {boolean} draft
|
||||
* @param {string|undefined} semver
|
||||
* @param {string|undefined} contract
|
||||
* @returns {Promise<import('./compose-types').Release>}
|
||||
*/
|
||||
export const createRelease = async function (
|
||||
apiEndpoint,
|
||||
auth,
|
||||
userId,
|
||||
appId,
|
||||
composition,
|
||||
draft,
|
||||
semver,
|
||||
contract,
|
||||
) {
|
||||
const _ = require('lodash');
|
||||
const crypto = require('crypto');
|
||||
const releaseMod = require('balena-release');
|
||||
apiEndpoint: string,
|
||||
auth: string,
|
||||
userId: number,
|
||||
appId: number,
|
||||
composition: Composition,
|
||||
draft: boolean,
|
||||
semver?: string,
|
||||
contract?: string,
|
||||
): Promise<Release> {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const crypto = require('crypto') as typeof import('crypto');
|
||||
const releaseMod =
|
||||
require('balena-release') as typeof import('balena-release');
|
||||
|
||||
const client = releaseMod.createClient({ apiEndpoint, auth });
|
||||
|
||||
@ -133,24 +137,26 @@ export const createRelease = async function (
|
||||
'start_timestamp',
|
||||
'end_timestamp',
|
||||
]),
|
||||
serviceImages: _.mapValues(serviceImages, (serviceImage) =>
|
||||
_.omit(serviceImage, [
|
||||
'created_at',
|
||||
'is_a_build_of__service',
|
||||
'__metadata',
|
||||
]),
|
||||
serviceImages: _.mapValues(
|
||||
serviceImages,
|
||||
(serviceImage) =>
|
||||
_.omit(serviceImage, [
|
||||
'created_at',
|
||||
'is_a_build_of__service',
|
||||
'__metadata',
|
||||
]) as Omit<
|
||||
typeof serviceImage,
|
||||
'created_at' | 'is_a_build_of__service' | '__metadata'
|
||||
>,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {Array<import('./compose-types').BuiltImage>} images
|
||||
* @param {Partial<import('balena-release/build/models').ImageModel>} serviceImages
|
||||
* @returns {Promise<Array<import('./compose-types').TaggedImage>>}
|
||||
*/
|
||||
export const tagServiceImages = (docker, images, serviceImages) =>
|
||||
export const tagServiceImages = (
|
||||
docker: Dockerode,
|
||||
images: BuiltImage[],
|
||||
serviceImages: Release['serviceImages'],
|
||||
): Promise<TaggedImage[]> =>
|
||||
Promise.all(
|
||||
images.map(function (d) {
|
||||
const serviceImage = serviceImages[d.serviceName];
|
||||
@ -177,25 +183,24 @@ export const tagServiceImages = (docker, images, serviceImages) =>
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {*} sdk
|
||||
* @param {import('./logger')} logger
|
||||
* @param {number} appID
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
export const getPreviousRepos = (
|
||||
sdk: SDK.BalenaSDK,
|
||||
logger: Logger,
|
||||
appID: number,
|
||||
): Promise<string[]> =>
|
||||
sdk.pine
|
||||
.get({
|
||||
.get<SDK.Release>({
|
||||
resource: 'release',
|
||||
options: {
|
||||
$select: 'id',
|
||||
$filter: {
|
||||
belongs_to__application: appID,
|
||||
status: 'success',
|
||||
},
|
||||
$select: ['id'],
|
||||
$expand: {
|
||||
contains__image: {
|
||||
$expand: 'image',
|
||||
$select: 'image',
|
||||
$expand: { image: { $select: 'is_stored_at__image_location' } },
|
||||
},
|
||||
},
|
||||
$orderby: 'id desc',
|
||||
@ -205,8 +210,11 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
.then(function (release) {
|
||||
// grab all images from the latest release, return all image locations in the registry
|
||||
if (release.length > 0) {
|
||||
const images = release[0].contains__image;
|
||||
const { getRegistryAndName } = require('resin-multibuild');
|
||||
const images = release[0].contains__image as Array<{
|
||||
image: [SDK.Image];
|
||||
}>;
|
||||
const { getRegistryAndName } =
|
||||
require('resin-multibuild') as typeof import('resin-multibuild');
|
||||
return Promise.all(
|
||||
images.map(function (d) {
|
||||
const imageName = d.image[0].is_stored_at__image_location || '';
|
||||
@ -226,21 +234,13 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
return [];
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {*} sdk
|
||||
* @param {string} tokenAuthEndpoint
|
||||
* @param {string} registry
|
||||
* @param {string[]} images
|
||||
* @param {string[]} previousRepos
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const authorizePush = function (
|
||||
sdk,
|
||||
tokenAuthEndpoint,
|
||||
registry,
|
||||
images,
|
||||
previousRepos,
|
||||
) {
|
||||
sdk: SDK.BalenaSDK,
|
||||
tokenAuthEndpoint: string,
|
||||
registry: string,
|
||||
images: string[],
|
||||
previousRepos: string[],
|
||||
): Promise<string> {
|
||||
if (!Array.isArray(images)) {
|
||||
images = [images];
|
||||
}
|
||||
@ -261,17 +261,20 @@ export const authorizePush = function (
|
||||
|
||||
// utilities
|
||||
|
||||
const renderProgressBar = function (percentage, stepCount) {
|
||||
const _ = require('lodash');
|
||||
const renderProgressBar = function (percentage: number, stepCount: number) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
percentage = _.clamp(percentage, 0, 100);
|
||||
const barCount = Math.floor((stepCount * percentage) / 100);
|
||||
const spaceCount = stepCount - barCount;
|
||||
const bar = `[${_.repeat('=', barCount)}>${_.repeat(' ', spaceCount)}]`;
|
||||
return `${bar} ${_.padStart(percentage, 3)}%`;
|
||||
return `${bar} ${_.padStart(`${percentage}`, 3)}%`;
|
||||
};
|
||||
|
||||
export const pushProgressRenderer = function (tty, prefix) {
|
||||
const fn = function (e) {
|
||||
export const pushProgressRenderer = function (
|
||||
tty: ReturnType<typeof import('./tty')>,
|
||||
prefix: string,
|
||||
): ProgressCallback & { end: () => void } {
|
||||
const fn: ProgressCallback & { end: () => void } = function (e) {
|
||||
const { error, percentage } = e;
|
||||
if (error != null) {
|
||||
throw new Error(error);
|
||||
@ -285,14 +288,39 @@ export const pushProgressRenderer = function (tty, prefix) {
|
||||
return fn;
|
||||
};
|
||||
|
||||
export class BuildProgressUI {
|
||||
constructor(tty, descriptors) {
|
||||
export class BuildProgressUI implements Renderer {
|
||||
public streams;
|
||||
private _prefix;
|
||||
private _prefixWidth;
|
||||
private _tty;
|
||||
private _services;
|
||||
private _startTime: undefined | number;
|
||||
private _ended;
|
||||
private _serviceToDataMap: Dictionary<{
|
||||
status?: string;
|
||||
progress?: number;
|
||||
error?: Error;
|
||||
}> = {};
|
||||
private _cancelled;
|
||||
private _spinner;
|
||||
private _runloop:
|
||||
| undefined
|
||||
| ReturnType<typeof import('./compose_ts').createRunLoop>;
|
||||
|
||||
// these are to handle window wrapping
|
||||
private _maxLineWidth: undefined | number;
|
||||
private _lineWidths: number[] = [];
|
||||
|
||||
constructor(
|
||||
tty: ReturnType<typeof import('./tty')>,
|
||||
descriptors: ImageDescriptor[],
|
||||
) {
|
||||
this._handleEvent = this._handleEvent.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
this.end = this.end.bind(this);
|
||||
this._display = this._display.bind(this);
|
||||
const _ = require('lodash');
|
||||
const through = require('through2');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const through = require('through2') as typeof import('through2');
|
||||
|
||||
const eventHandler = this._handleEvent;
|
||||
const services = _.map(descriptors, 'serviceName');
|
||||
@ -310,7 +338,6 @@ export class BuildProgressUI {
|
||||
.value();
|
||||
|
||||
this._tty = tty;
|
||||
this._serviceToDataMap = {};
|
||||
this._services = services;
|
||||
|
||||
// Logger magically prefixes the log line with [Build] etc., but it doesn't
|
||||
@ -320,22 +347,22 @@ export class BuildProgressUI {
|
||||
|
||||
const offset = 10; // account for escape sequences inserted for colouring
|
||||
this._prefixWidth =
|
||||
offset + prefix.length + _.max(_.map(services, 'length'));
|
||||
offset + prefix.length + _.max(_.map(services, (s) => s.length))!;
|
||||
this._prefix = prefix;
|
||||
|
||||
// these are to handle window wrapping
|
||||
this._maxLineWidth = null;
|
||||
this._lineWidths = [];
|
||||
|
||||
this._startTime = null;
|
||||
this._ended = false;
|
||||
this._cancelled = false;
|
||||
this._spinner = require('./compose_ts').createSpinner();
|
||||
this._spinner = (
|
||||
require('./compose_ts') as typeof import('./compose_ts')
|
||||
).createSpinner();
|
||||
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
_handleEvent(service, event) {
|
||||
_handleEvent(
|
||||
service: string,
|
||||
event: { status?: string; progress?: number; error?: Error },
|
||||
) {
|
||||
this._serviceToDataMap[service] = event;
|
||||
}
|
||||
|
||||
@ -344,20 +371,19 @@ export class BuildProgressUI {
|
||||
this._services.forEach((service) => {
|
||||
this.streams[service].write({ status: 'Preparing...' });
|
||||
});
|
||||
this._runloop = require('./compose_ts').createRunLoop(this._display);
|
||||
this._runloop = (
|
||||
require('./compose_ts') as typeof import('./compose_ts')
|
||||
).createRunLoop(this._display);
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dictionary<string> | undefined} summary
|
||||
*/
|
||||
end(summary) {
|
||||
end(summary?: Dictionary<string>) {
|
||||
if (this._ended) {
|
||||
return;
|
||||
}
|
||||
this._ended = true;
|
||||
this._runloop?.end();
|
||||
this._runloop = null;
|
||||
this._runloop = undefined;
|
||||
|
||||
this._clear();
|
||||
this._renderStatus(true);
|
||||
@ -378,7 +404,7 @@ export class BuildProgressUI {
|
||||
}
|
||||
|
||||
_getServiceSummary() {
|
||||
const _ = require('lodash');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
|
||||
const services = this._services;
|
||||
const serviceToDataMap = this._serviceToDataMap;
|
||||
@ -405,11 +431,11 @@ export class BuildProgressUI {
|
||||
.value();
|
||||
}
|
||||
|
||||
_renderStatus(end) {
|
||||
end ??= false;
|
||||
|
||||
const moment = require('moment');
|
||||
require('moment-duration-format')(moment);
|
||||
_renderStatus(end = false) {
|
||||
const moment = require('moment') as typeof import('moment');
|
||||
(
|
||||
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||
)(moment);
|
||||
|
||||
this._tty.clearLine();
|
||||
this._tty.write(this._prefix);
|
||||
@ -434,11 +460,11 @@ export class BuildProgressUI {
|
||||
}
|
||||
}
|
||||
|
||||
_renderSummary(serviceToStrMap) {
|
||||
const _ = require('lodash');
|
||||
_renderSummary(serviceToStrMap: Dictionary<string>) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const chalk = getChalk();
|
||||
const truncate = require('cli-truncate');
|
||||
const strlen = require('string-width');
|
||||
const truncate = require('cli-truncate') as typeof import('cli-truncate');
|
||||
const strlen = require('string-width') as typeof import('string-width');
|
||||
|
||||
this._services.forEach((service, index) => {
|
||||
let str = _.padEnd(this._prefix + chalk.bold(service), this._prefixWidth);
|
||||
@ -454,13 +480,23 @@ export class BuildProgressUI {
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildProgressInline {
|
||||
constructor(outStream, descriptors) {
|
||||
export class BuildProgressInline implements Renderer {
|
||||
public streams;
|
||||
private _prefixWidth;
|
||||
private _outStream;
|
||||
private _services;
|
||||
private _startTime: number | undefined;
|
||||
private _ended;
|
||||
|
||||
constructor(
|
||||
outStream: NodeJS.ReadWriteStream,
|
||||
descriptors: Array<{ serviceName: string }>,
|
||||
) {
|
||||
this.start = this.start.bind(this);
|
||||
this.end = this.end.bind(this);
|
||||
this._renderEvent = this._renderEvent.bind(this);
|
||||
const _ = require('lodash');
|
||||
const through = require('through2');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const through = require('through2') as typeof import('through2');
|
||||
|
||||
const services = _.map(descriptors, 'serviceName');
|
||||
const eventHandler = this._renderEvent;
|
||||
@ -477,10 +513,9 @@ export class BuildProgressInline {
|
||||
.value();
|
||||
|
||||
const offset = 10; // account for escape sequences inserted for colouring
|
||||
this._prefixWidth = offset + _.max(_.map(services, 'length'));
|
||||
this._prefixWidth = offset + _.max(_.map(services, (s) => s.length))!;
|
||||
this._outStream = outStream;
|
||||
this._services = services;
|
||||
this._startTime = null;
|
||||
this._ended = false;
|
||||
|
||||
this.streams = streams;
|
||||
@ -494,12 +529,11 @@ export class BuildProgressInline {
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dictionary<string> | undefined} summary
|
||||
*/
|
||||
end(summary) {
|
||||
const moment = require('moment');
|
||||
require('moment-duration-format')(moment);
|
||||
end(summary?: Dictionary<string>) {
|
||||
const moment = require('moment') as typeof import('moment');
|
||||
(
|
||||
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||
)(moment);
|
||||
|
||||
if (this._ended) {
|
||||
return;
|
||||
@ -527,8 +561,8 @@ export class BuildProgressInline {
|
||||
this._outStream.write(`Built ${serviceStr} in ${durationStr}\n`);
|
||||
}
|
||||
|
||||
_renderEvent(service, event) {
|
||||
const _ = require('lodash');
|
||||
_renderEvent(service: string, event: { status?: string; error?: Error }) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
|
||||
const str = (function () {
|
||||
const { status, error } = event;
|
@ -235,7 +235,7 @@ interface BuildTaskPlus extends MultiBuild.BuildTask {
|
||||
logBuffer?: string[];
|
||||
}
|
||||
|
||||
interface Renderer {
|
||||
export interface Renderer {
|
||||
start: () => void;
|
||||
end: (buildSummaryByService?: Dictionary<string>) => void;
|
||||
streams: Dictionary<NodeJS.ReadWriteStream>;
|
||||
|
Loading…
Reference in New Issue
Block a user