From 0b0270f10d18194c2da3f43d2fcd6737cb2482a4 Mon Sep 17 00:00:00 2001 From: Edwin Joassart Date: Thu, 1 Jun 2023 16:31:57 +0200 Subject: [PATCH] patch: upgrade balena-lint to 6.2.2 --- lib/block-write-stream.ts | 9 +- lib/diskpart.ts | 127 ++++--- lib/migrator/copy-bootloader.ts | 50 +-- lib/migrator/helpers.ts | 130 ++++--- lib/migrator/index.ts | 270 ++++++++++----- lib/migrator/windows-commands.ts | 20 +- .../balena-s3-compressed-source.ts | 8 +- lib/source-destination/balena-s3-source.ts | 2 +- lib/source-destination/block-device.ts | 13 +- lib/source-destination/compressed-source.ts | 2 +- .../configured-source/configure.ts | 2 +- .../configured-source/configured-source.ts | 2 +- lib/source-destination/source-destination.ts | 6 +- package-lock.json | 319 ++++++------------ package.json | 3 +- tests/utils.spec.ts | 21 +- 16 files changed, 507 insertions(+), 477 deletions(-) diff --git a/lib/block-write-stream.ts b/lib/block-write-stream.ts index 9d097dc8..304f89d6 100644 --- a/lib/block-write-stream.ts +++ b/lib/block-write-stream.ts @@ -47,7 +47,7 @@ export class BlockWriteStream extends Writable { highWaterMark?: number; delayFirstBuffer?: boolean; maxRetries?: number; - startOffset?: number + startOffset?: number; }) { super({ objectMode: true, highWaterMark }); this.destination = destination; @@ -59,7 +59,12 @@ export class BlockWriteStream extends Writable { private async writeBuffer(buffer: Buffer, position: number): Promise { await retryOnTransientError( async () => { - await this.destination.write(buffer, 0, buffer.length, position + this.startOffset); + await this.destination.write( + buffer, + 0, + buffer.length, + position + this.startOffset, + ); }, this.maxRetries, RETRY_BASE_TIMEOUT, diff --git a/lib/diskpart.ts b/lib/diskpart.ts index 4b20f30e..5199c1d1 100644 --- a/lib/diskpart.ts +++ b/lib/diskpart.ts @@ -30,9 +30,9 @@ const DISKPART_RETRIES = 5; const PATTERN = /PHYSICALDRIVE(\d+)/i; // This module provides utility functions for disk partitioning and related disk -// level tasks. Presently it relies on the Windows 'diskpart' utility to implement +// level tasks. Presently it relies on the Windows 'diskpart' utility to implement // this functionality. -// Given this reliance, any new exported functions must throw an Error when used on +// Given this reliance, any new exported functions must throw an Error when used on // non-Windows platforms. Only the clean() function silently accepts non-Windows // platforms, for historical reasons. Of course it also is fine to implement a function // with support for other platforms, and we may do so generally in the future. @@ -57,7 +57,7 @@ class ExecError extends Error { const execFileAsync = async ( command: string, args: string[] = [], - options: ExecFileOptions = {} + options: ExecFileOptions = {}, ): Promise => { return await new Promise( (resolve: (res: ExecResult) => void, reject: (err: ExecError) => void) => { @@ -71,9 +71,9 @@ const execFileAsync = async ( } else { resolve({ stdout, stderr }); } - } + }, ); - } + }, ); }; @@ -97,19 +97,16 @@ const runDiskpart = async (commands: string[]): Promise => { if (platform() !== 'win32') { return ''; } - let output = { 'stdout':'', 'stderr':'' } + let output = { stdout: '', stderr: '' }; await withTmpFile({ keepOpen: false }, async (file: TmpFileResult) => { await fs.writeFile(file.path, commands.join('\r\n')); await withDiskpartMutex(async () => { - output = await execFileAsync('diskpart', [ - '/s', - file.path, - ]); + output = await execFileAsync('diskpart', ['/s', file.path]); debug('stdout:', output.stdout); debug('stderr:', output.stderr); }); }); - return output.stdout + return output.stdout; }; /** @@ -137,9 +134,9 @@ const prepareDeviceId = (device: string) => { export const clean = async (device: string): Promise => { debug('clean', device); if (platform() !== 'win32') { - return + return; } - + let deviceId; try { @@ -165,7 +162,7 @@ export const clean = async (device: string): Promise => { await delay(DISKPART_DELAY); } else { throw new Error( - `Couldn't clean the drive, ${error.message} (code ${error.code})` + `Couldn't clean the drive, ${error.message} (code ${error.code})`, ); } } @@ -183,11 +180,11 @@ export const clean = async (device: string): Promise => { */ export const shrinkPartition = async ( partition: string, - desiredMB?: number + desiredMB?: number, ) => { debug('shrink', partition, desiredMB); if (platform() !== 'win32') { - throw new Error("shrinkPartition() not available on this platform") + throw new Error('shrinkPartition() not available on this platform'); } try { @@ -196,7 +193,9 @@ export const shrinkPartition = async ( `shrink ${desiredMB ? 'DESIRED='.concat(desiredMB + '') : ''}`, ]); } catch (error) { - throw(`shrinkPartition: ${error}${error.stdout ? `\n${error.stdout}` : ''}`); + throw new Error( + `shrinkPartition: ${error}${error.stdout ? `\n${error.stdout}` : ''}`, + ); } }; @@ -204,7 +203,7 @@ export const shrinkPartition = async ( * * @param {string} device - device path * @param {number} sizeMB - size of the new partition (free space has to be present) - * @param {string} fs - default "fat32", possible "ntfs" the filesystem to format with + * @param {string} fsType - default "fat32", possible "ntfs" the filesystem to format with * @param {string} desiredLetter - letter to assign to the new volume, gets the next free letter by default * @example * createPartition('\\\\.\\PhysicalDrive2', 2048) @@ -214,12 +213,12 @@ export const shrinkPartition = async ( export const createPartition = async ( device: string, sizeMB: number, - fs?: 'exFAT' | 'fat32' | 'ntfs', + fsType?: 'exFAT' | 'fat32' | 'ntfs', label?: string, - desiredLetter?: string + desiredLetter?: string, ) => { if (platform() !== 'win32') { - throw new Error("createPartition() not available on this platform") + throw new Error('createPartition() not available on this platform'); } const deviceId = prepareDeviceId(device); @@ -228,11 +227,19 @@ export const createPartition = async ( `select disk ${deviceId}`, `create partition primary size=${sizeMB}`, `${desiredLetter ? 'assign letter='.concat(desiredLetter) : ''}`, - `${fs ? 'format fs='.concat(fs).concat(`label=${label ?? 'Balena Volume'}`.concat(' quick')) : ''}`, - `detail partition` - ]) + `${ + fsType + ? 'format fs=' + .concat(fsType) + .concat(`label=${label ?? 'Balena Volume'}`.concat(' quick')) + : '' + }`, + `detail partition`, + ]); } catch (error) { - throw(`createPartition: ${error}${error.stdout ? `\n${error.stdout}` : ''}`); + throw new Error( + `createPartition: ${error}${error.stdout ? `\n${error.stdout}` : ''}`, + ); } }; @@ -246,10 +253,12 @@ export const createPartition = async ( */ export const setPartitionOnlineStatus = async ( volume: string, - status: boolean + status: boolean, ) => { if (platform() !== 'win32') { - throw new Error("setPartitionOnlineStatus() not available on this platform") + throw new Error( + 'setPartitionOnlineStatus() not available on this platform', + ); } try { @@ -258,7 +267,11 @@ export const setPartitionOnlineStatus = async ( `${status ? 'online' : 'offline'} volume`, ]); } catch (error) { - throw(`setPartitionOnlineStatus: ${error}${error.stdout ? `\n${error.stdout}` : ''}`); + throw new Error( + `setPartitionOnlineStatus: ${error}${ + error.stdout ? `\n${error.stdout}` : '' + }`, + ); } }; @@ -275,7 +288,7 @@ export const setPartitionOnlineStatus = async ( */ export const findVolume = async ( device: string, - label: string + label: string, ): Promise => { const deviceId = prepareDeviceId(device); @@ -290,49 +303,49 @@ export const findVolume = async ( * Volume 4 NTFS Partition 530 MB Healthy Hidden */ if (platform() !== 'win32') { - throw new Error("findVolume() not available on this platform") + throw new Error('findVolume() not available on this platform'); } - let listText = '' + let listText = ''; try { - listText = await runDiskpart([ - `select disk ${deviceId}`, - `list volume` - ]); + listText = await runDiskpart([`select disk ${deviceId}`, `list volume`]); } catch (error) { - throw(`findVolume: ${error}${error.stdout ? `\n${error.stdout}` : ''}`); + throw new Error( + `findVolume: ${error}${error.stdout ? `\n${error.stdout}` : ''}`, + ); } let labelPos = -1; // Look for 'Label' in column headings; then compare text on subsequent rows // at that position for the expected label. - for (let line of listText.split('\n')) { + for (const line of listText.split('\n')) { if (labelPos < 0) { labelPos = line.indexOf('Label'); } else { const volMatch = line.match(/Volume\s+(\d+)/); - if (volMatch && (line.substring(labelPos, labelPos + label.length) == label)) { - return volMatch[1] + if ( + volMatch && + line.substring(labelPos, labelPos + label.length) === label + ) { + return volMatch[1]; } } } - return '' + return ''; }; /** * Provide unallocated space on disk, in KB - * + * * @param {string} device - device path * @example * getUnallocatedSize('\\\\.\\PhysicalDrive0') * .then(...) * .catch(...) */ -export const getUnallocatedSize = async ( - device: string -): Promise => { +export const getUnallocatedSize = async (device: string): Promise => { if (platform() !== 'win32') { - throw new Error("getUnallocatedSize() not available on this platform") + throw new Error('getUnallocatedSize() not available on this platform'); } const deviceId = prepareDeviceId(device); @@ -348,35 +361,37 @@ export const getUnallocatedSize = async ( * -------- ------------- ------- ------- --- --- * Disk 0 Online 50 GB 6158 MB * */ - let listText = '' + let listText = ''; try { - listText = await runDiskpart([ - `list disk` - ]); + listText = await runDiskpart([`list disk`]); } catch (error) { - throw(`getUnallocatedSize: ${error}${error.stdout ? `\n${error.stdout}` : ''}`); + throw new Error( + `getUnallocatedSize: ${error}${error.stdout ? `\n${error.stdout}` : ''}`, + ); } let freePos = -1; - // Look for 'Free' in column headings; then read size at that position + // Look for 'Free' in column headings; then read size at that position // on the row for the requested disk. - for (let line of listText.split('\n')) { + for (const line of listText.split('\n')) { if (freePos < 0) { freePos = line.indexOf('Free'); } else if (line.indexOf(`Disk ${deviceId}`) >= 0) { const freeMatch = line.substring(freePos).match(/(\d+)\s+(\w+)B/); if (freeMatch) { let res = Number(freeMatch[1]); - for (let units of ['K', 'M', 'G', 'T']) { - if (freeMatch[2] == units) { + for (const units of ['K', 'M', 'G', 'T']) { + if (freeMatch[2] === units) { return res; } else { res *= 1024; } } } - break; // should have matched; break to throw below + break; // should have matched; break to throw below } } - throw(`getUnallocatedSize: Can't read Free space on disk ${deviceId} from: ${listText}`); + throw new Error( + `getUnallocatedSize: Can't read Free space on disk ${deviceId} from: ${listText}`, + ); }; diff --git a/lib/migrator/copy-bootloader.ts b/lib/migrator/copy-bootloader.ts index 6f0f754d..48a96ec6 100644 --- a/lib/migrator/copy-bootloader.ts +++ b/lib/migrator/copy-bootloader.ts @@ -1,15 +1,14 @@ -import * as fileSystem from 'fs' -import * as fsPromises from 'fs/promises' +import * as fileSystem from 'fs'; +import * as fsPromises from 'fs/promises'; import * as path from 'path'; import { promisify } from 'util'; import { interact } from 'balena-image-fs'; - const transformFile = async ( fname: string, sourceRe: RegExp, - replText: string + replText: string, ) => { const data = await fsPromises.readFile(fname, 'utf-8'); const newValue = data.replace(sourceRe, replText); @@ -18,11 +17,11 @@ const transformFile = async ( /** * Copies bootloader files from image partition to a local path. Also transforms - * grub configuration files to provide a fallback boot to Windows if initial + * grub configuration files to provide a fallback boot to Windows if initial * boot to balenaOS fails. * @param {string} imagePath - Pathname for source balenaOS image file * @param {number} sourcePartition - 1-based index of partition within image containing bootloader - * @param {string} sourceFolderPath - Pathname for source folder containing bootloader + * @param {string} sourceFolderPath - Pathname for source folder containing bootloader * @param {string} targetFolderPath - Pathname for target folder for bootloader * @returns */ @@ -30,14 +29,17 @@ export const copyBootloaderFromImage = async ( imagePath: string, sourcePartition: number, sourceFolderPath: string, - targetFolderPath: string + targetFolderPath: string, ) => { const filesToCopy = await interact(imagePath, sourcePartition, async (fs) => { return await promisify(fs.readdir)(sourceFolderPath); }); - for (let fileName of filesToCopy) { - const fromPath = path.join(sourceFolderPath, fileName).split('\\').join('/'); + for (const fileName of filesToCopy) { + const fromPath = path + .join(sourceFolderPath, fileName) + .split('\\') + .join('/'); const toPath = path.join(targetFolderPath, fileName); console.log(`Copying: ${fromPath} \t~=>\t ${toPath}`); @@ -46,31 +48,35 @@ export const copyBootloaderFromImage = async ( const toTarget = fileSystem.createWriteStream(toPath); return await new Promise((resolve, reject) => { - // danger zone - fromBalenaFile - .on('error', reject) - .pipe(toTarget) - .on('error', reject) - .on('close', resolve); + // danger zone + fromBalenaFile + .on('error', reject) + .pipe(toTarget) + .on('error', reject) + .on('close', resolve); }); }); // add configuration for fallback boot to Windows - if (process.platform == 'win32') { + if (process.platform === 'win32') { try { - if (fileName.toLowerCase() == 'grubenv') { + if (fileName.toLowerCase() === 'grubenv') { // replace #'s to maintain file size of 1024 bytes await transformFile(toPath, /^#{17}/m, 'next_entry=flash\n'); - - } else if (fileName.toLowerCase() == 'grub.cfg') { + } else if (fileName.toLowerCase() === 'grub.cfg') { const data = await fsPromises.readFile(toPath, 'utf-8'); - const newData = data.replace(/^default=boot/m, 'load_env\nif [ "${next_entry}" != "" ] ; then\n set default="${next_entry}"\n set next_entry=\n save_env next_entry\nelse\n set default=windows\nfi'); + const newData = data.replace( + /^default=boot/m, + 'load_env\nif [ "${next_entry}" != "" ] ; then\n set default="${next_entry}"\n set next_entry=\n save_env next_entry\nelse\n set default=windows\nfi', + ); // simplest to just append windows menuentry - const newData2 = newData.concat('\nmenuentry \x27windows\x27{\ninsmod chain\nsearch -s root -f \/EFI\/Microsoft\/Boot\/BCD\nchainloader \/EFI\/Microsoft\/Boot\/bootmgfw.efi\n}'); + const newData2 = newData.concat( + '\nmenuentry \x27windows\x27{\ninsmod chain\nsearch -s root -f /EFI/Microsoft/Boot/BCD\nchainloader /EFI/Microsoft/Boot/bootmgfw.efi\n}', + ); await fsPromises.writeFile(toPath, newData2, 'utf-8'); } } catch (error) { - throw(`transformFile: ${error}`); + throw new Error(`transformFile: ${error}`); } } } diff --git a/lib/migrator/helpers.ts b/lib/migrator/helpers.ts index 472339aa..46847e0c 100644 --- a/lib/migrator/helpers.ts +++ b/lib/migrator/helpers.ts @@ -1,9 +1,9 @@ import * as process from 'process'; -import * as drivelist from 'drivelist' +import * as drivelist from 'drivelist'; import { GetPartitionsResult, GPTPartition, MBRPartition } from 'partitioninfo'; import { BlockDevice, SourceDestination, File } from '../source-destination'; -export const MS_DATA_PARTITION_ID = 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' +export const MS_DATA_PARTITION_ID = 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7'; /** * Finds partitions in newTable with a partition offset not found in oldTable. @@ -12,63 +12,69 @@ export const MS_DATA_PARTITION_ID = 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' */ export const findNewPartitions = ( oldTable?: GetPartitionsResult, - newTable?: GetPartitionsResult + newTable?: GetPartitionsResult, ) => { - if (newTable?.partitions === undefined || oldTable?.partitions === undefined) { - throw Error('Partitions undefined in request') + if ( + newTable?.partitions === undefined || + oldTable?.partitions === undefined + ) { + throw Error('Partitions undefined in request'); } - return (newTable.partitions as Array).filter((n) => { - return !oldTable.partitions.some((o) => o.offset === n.offset) - }) -} + return (newTable.partitions as Array).filter( + (n) => { + return !oldTable.partitions.some((o) => o.offset === n.offset); + }, + ); +}; /** * Scans the filesystem on the partitions for the provided device for the filesystem * label text, assuming the filesystem is the provided type. * * Partition type must be GPT. - * + * * @returns partition containing the label, or null if not found */ export const findFilesystemLabel = async ( table: GetPartitionsResult, device: BlockDevice, label: string, - fs: 'fat16' | 'ext4' + fs: 'fat16' | 'ext4', ): Promise => { - - if (table.type == 'mbr') { - throw Error("Can't read MBR table") + if (table.type === 'mbr') { + throw Error("Can't read MBR table"); } // Only check non-system partitions on Windows - let partitions = table.partitions - if (process.platform == 'win32') { - partitions = table.partitions.filter(p => p.type.toUpperCase() == MS_DATA_PARTITION_ID) + let partitions = table.partitions; + if (process.platform === 'win32') { + partitions = table.partitions.filter( + (p) => p.type.toUpperCase() === MS_DATA_PARTITION_ID, + ); } // Determine label offset - let offset = 0 - if (fs == 'fat16') { + let offset = 0; + if (fs === 'fat16') { // https://en.wikipedia.org/wiki/Desian_of_the_FAT_file_system#Extended_BIOS_Parameter_Block - offset = 0x2B - } else if (fs == 'ext4') { + offset = 0x2b; + } else if (fs === 'ext4') { // https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html - offset = 0x400 + 0x78 + offset = 0x400 + 0x78; } - let buf = Buffer.alloc(label.length) + const buf = Buffer.alloc(label.length); for (const p of partitions) { // Satisfy TypeScript that p is not an MBRPartition even though we tested above on the table - if (! ('guid' in p)) { - continue + if (!('guid' in p)) { + continue; } - await device.read(buf, 0, buf.length, p.offset + offset) - if (buf.toString() == label) { + await device.read(buf, 0, buf.length, p.offset + offset); + if (buf.toString() === label) { return p; } } - return null -} + return null; +}; /** * Provides the boundary offsets for a partition on a device @@ -78,17 +84,19 @@ export const findFilesystemLabel = async ( */ export const getPartitionBoundaries = async ( source: SourceDestination, - partitionIndex = 1 + partitionIndex = 1, ) => { const partitioninfo = await source.getPartitionTable(); const partitions = partitioninfo?.partitions; if (partitions === undefined) { throw Error("Can't read partitions"); } - const currentPartition: MBRPartition | GPTPartition = (partitions as Array).filter(p => p.index === partitionIndex)[0]; + const currentPartition: MBRPartition | GPTPartition = ( + partitions as any[] + ).filter((p) => p.index === partitionIndex)[0]; const start = currentPartition.offset; - let end: number | undefined = undefined; + let end: number | undefined; if (start) { end = start + currentPartition.size; } @@ -102,29 +110,31 @@ export const getPartitionBoundaries = async ( * Calculate the size required for a partition to contain the contents of * the provided source partition. On Windows, rounds up to nearest MB due to * limitations of partitioning tools. - * + * * @param {SourceDestination} source - Device containing requested partition * @param {number} partitionIndex - 1-based index of requested partition * @returns calculated size in bytes */ export const calcRequiredPartitionSize = async ( source: SourceDestination, - partitionIndex = 1 + partitionIndex = 1, ) => { const sourceBoundaries = await getPartitionBoundaries(source, partitionIndex); const sourcePartitionSize = sourceBoundaries.end! - sourceBoundaries.start!; const alignmentBuffer = 4096; if (isNaN(sourcePartitionSize)) { - throw Error("Not able to find source partition size."); + throw Error('Not able to find source partition size.'); } - if (process.platform == 'win32') { - let sizeMB = Math.ceil((sourcePartitionSize + alignmentBuffer) / (1024 * 1024)); + if (process.platform === 'win32') { + const sizeMB = Math.ceil( + (sourcePartitionSize + alignmentBuffer) / (1024 * 1024), + ); return sizeMB * 1024 * 1024; } else { return sourcePartitionSize + alignmentBuffer; - } -} + } +}; /** * Copy a partition, referenced by index, from an image file to a block device, @@ -138,20 +148,24 @@ export const copyPartitionFromImageToDevice = async ( source: File, sourcePartitionIndex: number, target: BlockDevice, - targetOffset: number + targetOffset: number, ) => { - const sourceBoundaries = await getPartitionBoundaries(source, sourcePartitionIndex); - const sourcePartitionSize = - sourceBoundaries.end! - sourceBoundaries.start!; + const sourceBoundaries = await getPartitionBoundaries( + source, + sourcePartitionIndex, + ); + const sourcePartitionSize = sourceBoundaries.end! - sourceBoundaries.start!; if (isNaN(targetOffset) || isNaN(sourcePartitionSize)) { - throw Error("Not able to find source partition size or target offset."); + throw Error('Not able to find source partition size or target offset.'); } - const alignments = [source.getAlignment(), target.getAlignment()].filter((a) => a !== undefined) as number[] + const alignments = [source.getAlignment(), target.getAlignment()].filter( + (a) => a !== undefined, + ) as number[]; let alignment; if (alignments.length) { - alignment = Math.max(...alignments) + alignment = Math.max(...alignments); } const sourceReadStream = await source.createReadStream({ @@ -163,15 +177,17 @@ export const copyPartitionFromImageToDevice = async ( sourceReadStream.on('progress', (c) => { // console.clear(); - console.log(`read: ${JSON.stringify(c)}`) + console.log(`read: ${JSON.stringify(c)}`); }); target.open(); - const targetStream = await target.createWriteStream({startOffset: targetOffset}); + const targetStream = await target.createWriteStream({ + startOffset: targetOffset, + }); targetStream.on('progress', (p) => { - //console.clear(); - console.log(`write: ${JSON.stringify(p)}`) - }) + // console.clear(); + console.log(`write: ${JSON.stringify(p)}`); + }); return new Promise((resolve, reject) => { sourceReadStream @@ -180,7 +196,7 @@ export const copyPartitionFromImageToDevice = async ( .on('error', reject) .on('close', resolve); }); -} +}; /** * Creates a block device for the drive on this machine device named with the requested label. @@ -189,12 +205,14 @@ export const copyPartitionFromImageToDevice = async ( export const getTargetBlockDevice = async (mountLabel: string = 'C') => { const drives = await drivelist.list(); - const drive = drives.filter((d) => d.mountpoints.some(m => m.path.startsWith(mountLabel)))[0] + const drive = drives.filter((d) => + d.mountpoints.some((m) => m.path.startsWith(mountLabel)), + )[0]; return new BlockDevice({ drive, unmountOnSuccess: false, direct: true, write: true, - keepOriginal: true - }) -} + keepOriginal: true, + }); +}; diff --git a/lib/migrator/index.ts b/lib/migrator/index.ts index d9decd7f..f8b926be 100644 --- a/lib/migrator/index.ts +++ b/lib/migrator/index.ts @@ -9,7 +9,7 @@ import { calcRequiredPartitionSize, getTargetBlockDevice, findNewPartitions, - findFilesystemLabel + findFilesystemLabel, } from './helpers'; import { copyBootloaderFromImage } from './copy-bootloader'; import winCommands from './windows-commands'; @@ -18,32 +18,32 @@ import { promisify } from 'util'; import { exec as childExec } from 'child_process'; const execAsync = promisify(childExec); import { constants as osConstants } from 'os'; -import { existsSync } from 'fs' +import { existsSync } from 'fs'; /** Determine if running as administrator. */ async function isElevated(): Promise { - // `fltmc` is available on WinPE, XP, Vista, 7, 8, and 10 - // Works even when the "Server" service is disabled - // See http://stackoverflow.com/a/28268802 - try { - await execAsync('fltmc'); - } catch (error: any) { - if (error.code === osConstants.errno.EPERM) { - return false; - } - throw error; - } - return true; + // `fltmc` is available on WinPE, XP, Vista, 7, 8, and 10 + // Works even when the "Server" service is disabled + // See http://stackoverflow.com/a/28268802 + try { + await execAsync('fltmc'); + } catch (error: any) { + if (error.code === osConstants.errno.EPERM) { + return false; + } + throw error; + } + return true; } function formatMB(bytes: number): string { - return (bytes / (1024 * 1024)).toFixed(2) + return (bytes / (1024 * 1024)).toFixed(2); } /** Options for migrate(): */ export interface MigrateOptions { // don't perform these tasks; comma separated list like 'bootloader,reboot' - omitTasks: string + omitTasks: string; } /** @@ -75,7 +75,7 @@ export const migrate = async ( windowsPartition: string = 'C', deviceName: string = '\\\\.\\PhysicalDrive0', efiLabel: string = 'M', - options: MigrateOptions = { omitTasks: '' } + options: MigrateOptions = { omitTasks: '' }, ) => { console.log(`Migrate ${deviceName} with image ${imagePath}`); try { @@ -85,68 +85,94 @@ export const migrate = async ( const ROOTA_PARTITION_LABEL = 'flash-rootA'; const BOOT_FILES_SOURCE_PATH = '/EFI/BOOT'; const BOOT_FILES_TARGET_PATH = '/EFI/Boot'; - const REBOOT_DELAY_SEC = 10 - const ALL_TASKS = [ 'shrink', 'copy', 'bootloader', 'reboot']; + const REBOOT_DELAY_SEC = 10; + const ALL_TASKS = ['shrink', 'copy', 'bootloader', 'reboot']; // initial validations if (process.platform !== 'win32') { - throw Error("Platform is not Windows"); + throw Error('Platform is not Windows'); } if (!(await isElevated())) { - throw Error("User is not administrator"); + throw Error('User is not administrator'); } if (!existsSync(imagePath)) { throw Error(`Image ${imagePath} not found`); } - const tasks = ALL_TASKS.filter(task => !options.omitTasks.includes(task)); + const tasks = ALL_TASKS.filter((task) => !options.omitTasks.includes(task)); // Define objects for image file source for partitions, storage device target, // and the target's partition table. const source = new File({ path: imagePath }); - const targetDevice = await getTargetBlockDevice(windowsPartition) - let currentPartitions = await targetDevice.getPartitionTable() + const targetDevice = await getTargetBlockDevice(windowsPartition); + let currentPartitions = await targetDevice.getPartitionTable(); if (currentPartitions === undefined) { throw Error("Can't read partition table"); } // Log existing partitions for debugging - console.log("\nPartitions on target:") + console.log('\nPartitions on target:'); for (const p of currentPartitions.partitions) { // Satisfy TypeScript that p is not an MBRPartition even though we tested above on the table if (!('guid' in p)) { - continue + continue; } - console.log(`index ${p.index}, offset ${p.offset}, type ${p.type}`) + console.log(`index ${p.index}, offset ${p.offset}, type ${p.type}`); } // Prepare to check for the balenaOS boot and rootA partitions already present. // If partitions not present, determine required partition sizes and free space. // Calculations are in units of bytes. However, on Windows, required sizes are // rounded up to the nearest MB due to tool limitations. - let targetBootPartition: GPTPartition | MBRPartition | null - let targetRootAPartition: GPTPartition | MBRPartition | null - let requiredBootSize = 0 - let requiredRootASize = 0 + let targetBootPartition: GPTPartition | MBRPartition | null; + let targetRootAPartition: GPTPartition | MBRPartition | null; + let requiredBootSize = 0; + let requiredRootASize = 0; // Look for boot partition on a FAT16 filesystem - targetBootPartition = await findFilesystemLabel(currentPartitions, targetDevice, - BOOT_PARTITION_LABEL, 'fat16') + targetBootPartition = await findFilesystemLabel( + currentPartitions, + targetDevice, + BOOT_PARTITION_LABEL, + 'fat16', + ); if (targetBootPartition) { - console.log(`Boot partition already exists at index ${targetBootPartition.index}`) + console.log( + `Boot partition already exists at index ${targetBootPartition.index}`, + ); } else { - console.log("Boot partition not found on target") - requiredBootSize = await calcRequiredPartitionSize(source, BOOT_PARTITION_INDEX); - console.log(`Require ${requiredBootSize} (${formatMB(requiredBootSize)} MB) for boot partition`); + console.log('Boot partition not found on target'); + requiredBootSize = await calcRequiredPartitionSize( + source, + BOOT_PARTITION_INDEX, + ); + console.log( + `Require ${requiredBootSize} (${formatMB( + requiredBootSize, + )} MB) for boot partition`, + ); } // Look for rootA partition on an ext4 filesystem - targetRootAPartition = await findFilesystemLabel(currentPartitions, targetDevice, - ROOTA_PARTITION_LABEL, 'ext4') + targetRootAPartition = await findFilesystemLabel( + currentPartitions, + targetDevice, + ROOTA_PARTITION_LABEL, + 'ext4', + ); if (targetRootAPartition) { - console.log(`RootA partition already exists at index ${targetRootAPartition.index}`) + console.log( + `RootA partition already exists at index ${targetRootAPartition.index}`, + ); } else { - console.log("RootA partition not found on target") - requiredRootASize = await calcRequiredPartitionSize(source, ROOTA_PARTITION_INDEX); - console.log(`Require ${requiredRootASize} (${formatMB(requiredRootASize)} MB) for rootA partition`) + console.log('RootA partition not found on target'); + requiredRootASize = await calcRequiredPartitionSize( + source, + ROOTA_PARTITION_INDEX, + ); + console.log( + `Require ${requiredRootASize} (${formatMB( + requiredRootASize, + )} MB) for rootA partition`, + ); } const requiredFreeSize = requiredBootSize + requiredRootASize; @@ -154,102 +180,166 @@ export const migrate = async ( // Shrink amount must be for *all* of required space to ensure it is contiguous. // IOW, don't assume the shrink will merge with any existing unallocated space. if (requiredFreeSize) { - const unallocSpace = (await diskpart.getUnallocatedSize(deviceName)) * 1024; - console.log(`Found ${unallocSpace} (${formatMB(unallocSpace)} MB) not allocated on disk ${deviceName}`) + const unallocSpace = + (await diskpart.getUnallocatedSize(deviceName)) * 1024; + console.log( + `Found ${unallocSpace} (${formatMB( + unallocSpace, + )} MB) not allocated on disk ${deviceName}`, + ); if (unallocSpace < requiredFreeSize) { // must force upper case - const freeSpace = await checkDiskSpace(`${windowsPartition.toUpperCase()}:\\`) + const freeSpace = await checkDiskSpace( + `${windowsPartition.toUpperCase()}:\\`, + ); if (freeSpace.free < requiredFreeSize) { - throw Error(`Need at least ${requiredFreeSize} (${formatMB(requiredFreeSize)} MB) free on partition ${windowsPartition}`) + throw Error( + `Need at least ${requiredFreeSize} (${formatMB( + requiredFreeSize, + )} MB) free on partition ${windowsPartition}`, + ); } if (tasks.includes('shrink')) { - console.log(`\nShrink partition ${windowsPartition} by ${requiredFreeSize} (${formatMB(requiredFreeSize)} MB)`); - await diskpart.shrinkPartition(windowsPartition, requiredFreeSize / (1024 * 1024)); + console.log( + `\nShrink partition ${windowsPartition} by ${requiredFreeSize} (${formatMB( + requiredFreeSize, + )} MB)`, + ); + await diskpart.shrinkPartition( + windowsPartition, + requiredFreeSize / (1024 * 1024), + ); } else { - console.log(`\nSkip task: shrink partition ${windowsPartition} by ${requiredFreeSize} (${formatMB(requiredFreeSize)} MB)`); + console.log( + `\nSkip task: shrink partition ${windowsPartition} by ${requiredFreeSize} (${formatMB( + requiredFreeSize, + )} MB)`, + ); } - } else{ - console.log("Unallocated space on target is sufficient for copy") + } else { + console.log('Unallocated space on target is sufficient for copy'); } } if (tasks.includes('copy')) { // create partitions - console.log("") //force newline - let volumeIds = ['', ''] + console.log(''); // force newline + const volumeIds = ['', '']; if (!targetBootPartition) { - console.log("Create flasherBootPartition"); - await diskpart.createPartition(deviceName, requiredBootSize / (1024 * 1024)); - const afterFirstPartitions = await targetDevice.getPartitionTable() - const firstNewPartition = findNewPartitions(currentPartitions, afterFirstPartitions); + console.log('Create flasherBootPartition'); + await diskpart.createPartition( + deviceName, + requiredBootSize / (1024 * 1024), + ); + const afterFirstPartitions = await targetDevice.getPartitionTable(); + const firstNewPartition = findNewPartitions( + currentPartitions, + afterFirstPartitions, + ); if (firstNewPartition.length !== 1) { - throw Error(`Found ${firstNewPartition.length} new partitions for flasher boot, but expected 1`) + throw Error( + `Found ${firstNewPartition.length} new partitions for flasher boot, but expected 1`, + ); } targetBootPartition = firstNewPartition[0]; - console.log(`Created new partition for boot at offset ${targetBootPartition.offset} with size ${targetBootPartition.size}`); - currentPartitions = afterFirstPartitions + console.log( + `Created new partition for boot at offset ${targetBootPartition.offset} with size ${targetBootPartition.size}`, + ); + currentPartitions = afterFirstPartitions; } - volumeIds[0] = await diskpart.findVolume(deviceName, BOOT_PARTITION_LABEL) - console.log(`flasherBootPartition volume: ${volumeIds[0]}`) + volumeIds[0] = await diskpart.findVolume( + deviceName, + BOOT_PARTITION_LABEL, + ); + console.log(`flasherBootPartition volume: ${volumeIds[0]}`); if (!targetRootAPartition) { - console.log("Create flasherRootAPartition"); - await diskpart.createPartition(deviceName, requiredRootASize / (1024 * 1024)); - const afterSecondPartitions = await targetDevice.getPartitionTable() - const secondNewPartition = findNewPartitions(currentPartitions, afterSecondPartitions) + console.log('Create flasherRootAPartition'); + await diskpart.createPartition( + deviceName, + requiredRootASize / (1024 * 1024), + ); + const afterSecondPartitions = await targetDevice.getPartitionTable(); + const secondNewPartition = findNewPartitions( + currentPartitions, + afterSecondPartitions, + ); if (secondNewPartition.length !== 1) { - throw Error(`Found ${secondNewPartition.length} new partitions for flasher rootA, but expected 1`) + throw Error( + `Found ${secondNewPartition.length} new partitions for flasher rootA, but expected 1`, + ); } targetRootAPartition = secondNewPartition[0]; - console.log(`Created new partition for data at offset ${targetRootAPartition.offset} with size ${targetRootAPartition.size}`); - currentPartitions = afterSecondPartitions + console.log( + `Created new partition for data at offset ${targetRootAPartition.offset} with size ${targetRootAPartition.size}`, + ); + currentPartitions = afterSecondPartitions; } - volumeIds[1] = await diskpart.findVolume(deviceName, ROOTA_PARTITION_LABEL) - console.log(`flasherRootAPartition volume: ${volumeIds[1]}`) + volumeIds[1] = await diskpart.findVolume( + deviceName, + ROOTA_PARTITION_LABEL, + ); + console.log(`flasherRootAPartition volume: ${volumeIds[1]}`); // copy partition data // Use volume ID to take volume offine. At present really only necessary // when overwriting boot partition because Windows recognizes the filesystem // and will not allow overwriting it. No need to bring a volume back online. - console.log("Copy flasherBootPartition from image to disk"); + console.log('Copy flasherBootPartition from image to disk'); if (volumeIds[0]) { - await diskpart.setPartitionOnlineStatus(volumeIds[0], false) + await diskpart.setPartitionOnlineStatus(volumeIds[0], false); } - await copyPartitionFromImageToDevice(source, 1, targetDevice, targetBootPartition!.offset); - console.log("Copy complete") - console.log("Copy flasherRootAPartition from image to disk"); + await copyPartitionFromImageToDevice( + source, + 1, + targetDevice, + targetBootPartition!.offset, + ); + console.log('Copy complete'); + console.log('Copy flasherRootAPartition from image to disk'); if (volumeIds[1]) { - await diskpart.setPartitionOnlineStatus(volumeIds[1], false) + await diskpart.setPartitionOnlineStatus(volumeIds[1], false); } - await copyPartitionFromImageToDevice(source, 2, targetDevice, targetRootAPartition!.offset); - console.log("Copy complete") + await copyPartitionFromImageToDevice( + source, + 2, + targetDevice, + targetRootAPartition!.offset, + ); + console.log('Copy complete'); } else { - console.log(`\nSkip task: create and copy partitions`) + console.log(`\nSkip task: create and copy partitions`); } if (tasks.includes('bootloader')) { // mount the boot partition and copy bootloader - console.log("\nMount Windows boot partition and copy grub bootloader from image"); + console.log( + '\nMount Windows boot partition and copy grub bootloader from image', + ); winCommands.mountEfi(efiLabel); - await copyBootloaderFromImage(imagePath, 1, BOOT_FILES_SOURCE_PATH, `${efiLabel ?? "M"}:${BOOT_FILES_TARGET_PATH}`); - console.log("Copied grub bootloader files"); + await copyBootloaderFromImage( + imagePath, + 1, + BOOT_FILES_SOURCE_PATH, + `${efiLabel ?? 'M'}:${BOOT_FILES_TARGET_PATH}`, + ); + console.log('Copied grub bootloader files'); // set boot file - console.log("Set boot file"); + console.log('Set boot file'); const setBootResult = await winCommands.setBoot(); - console.log("Boot file set.", setBootResult) + console.log('Boot file set.', setBootResult); } else { - console.log("\nSkip task: bootloader setup"); + console.log('\nSkip task: bootloader setup'); } if (tasks.includes('reboot')) { - console.log("Migration complete, about to reboot"); + console.log('Migration complete, about to reboot'); winCommands.shutdown.reboot(REBOOT_DELAY_SEC); } else { - console.log("Skip task: reboot"); + console.log('Skip task: reboot'); } - } catch (error) { console.log("Can't proceed with migration:", error); return; diff --git a/lib/migrator/windows-commands.ts b/lib/migrator/windows-commands.ts index a7b93cb0..07b97317 100644 --- a/lib/migrator/windows-commands.ts +++ b/lib/migrator/windows-commands.ts @@ -3,7 +3,7 @@ import * as process from 'process'; const checkPlatform = () => { if (process.platform !== 'win32') { - throw Error("This command can be run only on windows."); + throw Error('This command can be run only on windows.'); } }; @@ -17,7 +17,7 @@ const mountEfi = (desiredLetter: string = 'M') => { // unmount the letter // fails silently if not present, umounts if occupied try { - child_process.execSync(`mountvol ${desiredLetter}: /D`) + child_process.execSync(`mountvol ${desiredLetter}: /D`); } catch (error) { // noop } finally { @@ -27,7 +27,7 @@ const mountEfi = (desiredLetter: string = 'M') => { try { child_process.execSync(`mountvol ${desiredLetter}: /S`); } catch (error) { - throw(`mountEfi: ${error}`); + throw new Error(`mountEfi: ${error}`); } }; @@ -41,24 +41,28 @@ const setBoot = async (path: string = '\\EFI\\Boot\\bootx64.efi') => { return new Promise((resolve, reject) => { child_process.exec(`bcdedit /set {bootmgr} path ${path}`, (err, stdout) => { if (err) { - reject(`setBoot: ${err}${stdout ? `\n${stdout}` : ""}`); + reject(`setBoot: ${err}${stdout ? `\n${stdout}` : ''}`); } resolve(stdout); }); - }) + }); }; const shutdown = { reboot: (delay: number = 0) => { child_process.exec(`shutdown /r /t ${delay}`, function (err, _, __) { - if (err) throw Error(err.message); + if (err) { + throw Error(err.message); + } console.log(`Rebooting in ${delay} seconds...`); }); }, now: () => { child_process.exec('shutdown /s /t 0', function (err, _, __) { - if (err) throw Error(err.message); - console.log("Shutting down"); + if (err) { + throw Error(err.message); + } + console.log('Shutting down'); }); }, }; diff --git a/lib/source-destination/balena-s3-compressed-source.ts b/lib/source-destination/balena-s3-compressed-source.ts index 67568ef2..5d22e9db 100644 --- a/lib/source-destination/balena-s3-compressed-source.ts +++ b/lib/source-destination/balena-s3-compressed-source.ts @@ -125,7 +125,8 @@ export class BalenaS3CompressedSource extends BalenaS3SourceBase { } private async getImageJSON(): Promise { - const imageJSON = (await this.download(`image${this.imageSuffix}.json`)).data; + const imageJSON = (await this.download(`image${this.imageSuffix}.json`)) + .data; return imageJSON; } @@ -136,7 +137,10 @@ export class BalenaS3CompressedSource extends BalenaS3SourceBase { private async getPartStream( filename: string, ): Promise { - const response = await this.download(`compressed${this.imageSuffix}/${filename}`, 'stream'); + const response = await this.download( + `compressed${this.imageSuffix}/${filename}`, + 'stream', + ); return response.data; } diff --git a/lib/source-destination/balena-s3-source.ts b/lib/source-destination/balena-s3-source.ts index da83e78f..16abc7e3 100644 --- a/lib/source-destination/balena-s3-source.ts +++ b/lib/source-destination/balena-s3-source.ts @@ -99,7 +99,7 @@ export abstract class BalenaS3SourceBase extends SourceDestination { } protected get imageSuffix(): string { - return this.imageType ? `-${this.imageType}`: '' + return this.imageType ? `-${this.imageType}` : ''; } public async canCreateReadStream(): Promise { diff --git a/lib/source-destination/block-device.ts b/lib/source-destination/block-device.ts index 0b773aea..2fb764df 100644 --- a/lib/source-destination/block-device.ts +++ b/lib/source-destination/block-device.ts @@ -47,7 +47,7 @@ export class BlockDevice extends File implements AdapterSourceDestination { private unmountOnSuccess: boolean; public oDirect: boolean; public emitsProgress = false; - private keepOriginal = false + private keepOriginal = false; public readonly alignment: number; constructor({ @@ -55,13 +55,13 @@ export class BlockDevice extends File implements AdapterSourceDestination { unmountOnSuccess = false, write = false, direct = true, - keepOriginal = false + keepOriginal = false, }: { drive: DrivelistDrive; unmountOnSuccess?: boolean; write?: boolean; direct?: boolean; - keepOriginal?: boolean + keepOriginal?: boolean; }) { super({ path: drive.raw, write }); this.drive = drive; @@ -150,12 +150,15 @@ export class BlockDevice extends File implements AdapterSourceDestination { public async createWriteStream({ highWaterMark, startOffset, - }: { highWaterMark?: number, startOffset?: number } = {}): Promise { + }: { + highWaterMark?: number; + startOffset?: number; + } = {}): Promise { const stream = new ProgressBlockWriteStream({ destination: this, delayFirstBuffer: platform() === 'win32', highWaterMark, - startOffset + startOffset, }); stream.on('finish', stream.emit.bind(stream, 'done')); return stream; diff --git a/lib/source-destination/compressed-source.ts b/lib/source-destination/compressed-source.ts index 3d1aa647..c3ef7f35 100644 --- a/lib/source-destination/compressed-source.ts +++ b/lib/source-destination/compressed-source.ts @@ -99,7 +99,7 @@ export abstract class CompressedSource extends SourceSource { } } catch (error) { // noop - console.log("Can't get size from partition table") + console.log("Can't get size from partition table"); } } diff --git a/lib/source-destination/configured-source/configure.ts b/lib/source-destination/configured-source/configure.ts index a0a83af4..44a9a2df 100644 --- a/lib/source-destination/configured-source/configure.ts +++ b/lib/source-destination/configured-source/configure.ts @@ -47,7 +47,7 @@ export interface DeviceTypeJSON { yocto: { archive?: boolean; }; - arch: string + arch: string; } const MBR_LAST_PRIMARY_PARTITION = 4; diff --git a/lib/source-destination/configured-source/configured-source.ts b/lib/source-destination/configured-source/configured-source.ts index 37a54d4b..aa5db179 100644 --- a/lib/source-destination/configured-source/configured-source.ts +++ b/lib/source-destination/configured-source/configured-source.ts @@ -266,7 +266,7 @@ export class ConfiguredSource extends SourceSource { try { await interact(this.disk, partition.index, async (fs) => { // @ts-ignore: trim method exists for ext partitions - if (fs.trim !== undefined) { + if (fs?.trim !== undefined) { // @ts-ignore: trim method exists for ext partitions await fs.trim(); } diff --git a/lib/source-destination/source-destination.ts b/lib/source-destination/source-destination.ts index 979814ce..c10e98ba 100644 --- a/lib/source-destination/source-destination.ts +++ b/lib/source-destination/source-destination.ts @@ -520,7 +520,9 @@ export class SourceDestination extends EventEmitter { try { mimetype = await this.getMimeTypeFromContent(); } catch (e) { - if (e.code === 'EISDIR') { throw e; } // expected to die on directories + if (e.code === 'EISDIR') { + throw e; + } // expected to die on directories console.log("Can't get mimetype from content", e.code); } @@ -540,7 +542,7 @@ export class SourceDestination extends EventEmitter { // no partitions } } catch (error) { - console.log("Can't read to buffer to get partitions") + console.log("Can't read to buffer to get partitions"); throw error; } } diff --git a/package-lock.json b/package-lock.json index f50657e1..8271dfaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "zip-part-stream": "^2.0.0" }, "devDependencies": { - "@balena/lint": "^5.4.0", + "@balena/lint": "^6.2.2", "@types/bluebird": "^3.5.23", "@types/chai": "^4.1.4", "@types/cli-spinner": "^0.2.0", @@ -1083,38 +1083,82 @@ } }, "node_modules/@balena/lint": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-5.4.2.tgz", - "integrity": "sha512-qnmJhQL9pjo1bB/k99w+pp2c0Snbp6lAH4QvF2JzzC0PufeExzRaQGhjsQuLIVspnAx7sTKyvPKnVIzMiy7FXw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.2.2.tgz", + "integrity": "sha512-PD1l90kE109lhj6+U2l1jgoDC92qkpI9EFREkqIv+aCc6ZtBvBBcyjCFaD8NMw46xom/TJYC3mD8ir3QsR5Xgg==", "dev": true, "dependencies": { "@types/glob": "^7.1.3", - "@types/lodash": "^4.14.167", - "@types/node": "^10.17.51", - "@types/prettier": "^2.1.6", - "coffee-script": "^1.10.0", - "coffeelint": "^1.15.0", - "coffeescope2": "^0.4.5", - "depcheck": "^1.3.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "prettier": "^2.2.1", + "@types/node": "^12.20.13", + "@types/prettier": "^2.2.3", + "depcheck": "^1.4.1", + "glob": "^7.1.7", + "prettier": "^2.3.0", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", - "typescript": "^4.1.3", + "typescript": "^5.0.2", "yargs": "^16.2.0" }, "bin": { "balena-lint": "bin/balena-lint" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" } }, "node_modules/@balena/lint/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", "dev": true }, + "node_modules/@balena/lint/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@balena/lint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@balena/lint/node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, "node_modules/@balena/node-beaglebone-usbboot": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@balena/node-beaglebone-usbboot/-/node-beaglebone-usbboot-3.0.0.tgz", @@ -2066,54 +2110,6 @@ "node": ">=0.10.0" } }, - "node_modules/coffee-script": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz", - "integrity": "sha512-NIWm59Fh1zkXq6TS6PQvSO3AR9DbGq1IBNZHa1E3fUCNmJhIwLf1YKcWgaHqaU7zWGC/OE2V7K3GVAXFzcmu+A==", - "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", - "dev": true, - "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/coffeelint": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/coffeelint/-/coffeelint-1.16.2.tgz", - "integrity": "sha512-6mzgOo4zb17WfdrSui/cSUEgQ0AQkW3gXDht+6lHkfkqGUtSYKwGdGcXsDfAyuScVzTlTtKdfwkAlJWfqul7zg==", - "dev": true, - "dependencies": { - "coffee-script": "~1.11.0", - "glob": "^7.0.6", - "ignore": "^3.0.9", - "optimist": "^0.6.1", - "resolve": "^0.6.3", - "strip-json-comments": "^1.0.2" - }, - "bin": { - "coffeelint": "bin/coffeelint" - }, - "engines": { - "node": ">=0.8.0", - "npm": ">=1.3.7" - } - }, - "node_modules/coffeescope2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/coffeescope2/-/coffeescope2-0.4.6.tgz", - "integrity": "sha1-FH8CcBXRWCP5eFl6uaEJQYGkHb0=", - "dev": true, - "dependencies": { - "globals": "^10.1.0" - }, - "engines": { - "node": ">=0.8", - "npm": "*" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2772,15 +2768,6 @@ "node": ">= 6" } }, - "node_modules/globals": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-10.4.0.tgz", - "integrity": "sha512-uNUtxIZpGyuaq+5BqGGQHsL4wUlJAXRqOm6g3Y48/CWNGTLONgBibI0lh6lGxjR2HljFYUfszb+mk4WkgMntsA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/gpt": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/gpt/-/gpt-2.0.4.tgz", @@ -2925,12 +2912,6 @@ } ] }, - "node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, "node_modules/immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", @@ -3401,12 +3382,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "dev": true - }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -3903,16 +3878,6 @@ "wrappy": "1" } }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -4351,12 +4316,6 @@ "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", "dev": true }, - "node_modules/resolve": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", - "integrity": "sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==", - "dev": true - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4649,18 +4608,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", - "dev": true, - "bin": { - "strip-json-comments": "cli.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -5222,15 +5169,6 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "optional": true }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/workerpool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", @@ -6348,33 +6286,57 @@ } }, "@balena/lint": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-5.4.2.tgz", - "integrity": "sha512-qnmJhQL9pjo1bB/k99w+pp2c0Snbp6lAH4QvF2JzzC0PufeExzRaQGhjsQuLIVspnAx7sTKyvPKnVIzMiy7FXw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.2.2.tgz", + "integrity": "sha512-PD1l90kE109lhj6+U2l1jgoDC92qkpI9EFREkqIv+aCc6ZtBvBBcyjCFaD8NMw46xom/TJYC3mD8ir3QsR5Xgg==", "dev": true, "requires": { "@types/glob": "^7.1.3", - "@types/lodash": "^4.14.167", - "@types/node": "^10.17.51", - "@types/prettier": "^2.1.6", - "coffee-script": "^1.10.0", - "coffeelint": "^1.15.0", - "coffeescope2": "^0.4.5", - "depcheck": "^1.3.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "prettier": "^2.2.1", + "@types/node": "^12.20.13", + "@types/prettier": "^2.2.3", + "depcheck": "^1.4.1", + "glob": "^7.1.7", + "prettier": "^2.3.0", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", - "typescript": "^4.1.3", + "typescript": "^5.0.2", "yargs": "^16.2.0" }, "dependencies": { "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true } } @@ -7185,35 +7147,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "coffee-script": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz", - "integrity": "sha512-NIWm59Fh1zkXq6TS6PQvSO3AR9DbGq1IBNZHa1E3fUCNmJhIwLf1YKcWgaHqaU7zWGC/OE2V7K3GVAXFzcmu+A==", - "dev": true - }, - "coffeelint": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/coffeelint/-/coffeelint-1.16.2.tgz", - "integrity": "sha512-6mzgOo4zb17WfdrSui/cSUEgQ0AQkW3gXDht+6lHkfkqGUtSYKwGdGcXsDfAyuScVzTlTtKdfwkAlJWfqul7zg==", - "dev": true, - "requires": { - "coffee-script": "~1.11.0", - "glob": "^7.0.6", - "ignore": "^3.0.9", - "optimist": "^0.6.1", - "resolve": "^0.6.3", - "strip-json-comments": "^1.0.2" - } - }, - "coffeescope2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/coffeescope2/-/coffeescope2-0.4.6.tgz", - "integrity": "sha1-FH8CcBXRWCP5eFl6uaEJQYGkHb0=", - "dev": true, - "requires": { - "globals": "^10.1.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -7704,12 +7637,6 @@ "is-glob": "^4.0.1" } }, - "globals": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-10.4.0.tgz", - "integrity": "sha512-uNUtxIZpGyuaq+5BqGGQHsL4wUlJAXRqOm6g3Y48/CWNGTLONgBibI0lh6lGxjR2HljFYUfszb+mk4WkgMntsA==", - "dev": true - }, "gpt": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/gpt/-/gpt-2.0.4.tgz", @@ -7810,12 +7737,6 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, "immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", @@ -8179,12 +8100,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "dev": true - }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -8592,16 +8507,6 @@ "wrappy": "1" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -8919,12 +8824,6 @@ "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", "dev": true }, - "resolve": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", - "integrity": "sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==", - "dev": true - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -9140,12 +9039,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", - "dev": true - }, "strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -9552,12 +9445,6 @@ } } }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "dev": true - }, "workerpool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", diff --git a/package.json b/package.json index dab07fca..0f49da95 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "npm run lint && mocha -r ts-node/register tests/**/*.spec.ts", "prettier": "balena-lint --fix lib typings examples tests", "lint": "balena-lint lib typings examples tests", + "lint:fix": "balena-lint --fix lib typings examples tests", "build": "tsc", "doc": "typedoc --readme none --theme markdown --mode file --out doc lib && npm run sed", "sed": "sed -i'.bak' 's|'$(pwd)'||g' $(find doc -type f) && rimraf doc/*.bak doc/**/*.bak", @@ -75,7 +76,7 @@ "winusb-driver-generator": "^2.0.0" }, "devDependencies": { - "@balena/lint": "^5.4.0", + "@balena/lint": "^6.2.2", "@types/bluebird": "^3.5.23", "@types/chai": "^4.1.4", "@types/cli-spinner": "^0.2.0", diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts index 795b92d7..fdc0a9ec 100644 --- a/tests/utils.spec.ts +++ b/tests/utils.spec.ts @@ -66,14 +66,11 @@ describe('utils', function () { describe('BalenaS3SourceBase', function () { describe('isESRVersion', function () { it('should return false for non-ESR versions following the original scheme', function () { - [ - '2.7.8.dev', - '2.7.8.prod', - '2.80.3.0.dev', - '2.80.3.0.prod', - ].forEach((v) => { - expect(BalenaS3SourceBase.isESRVersion(v)).to.be.false; - }); + ['2.7.8.dev', '2.7.8.prod', '2.80.3.0.dev', '2.80.3.0.prod'].forEach( + (v) => { + expect(BalenaS3SourceBase.isESRVersion(v)).to.be.false; + }, + ); }); it('should return false for unified non-ESR versions', function () { @@ -83,11 +80,9 @@ describe('utils', function () { }); it('should return true for ESR versions following the original scheme', function () { - ['2020.04.0.prod', '2021.10.1.dev', '2021.10.1.prod'].forEach( - (v) => { - expect(BalenaS3SourceBase.isESRVersion(v)).to.be.true; - }, - ); + ['2020.04.0.prod', '2021.10.1.dev', '2021.10.1.prod'].forEach((v) => { + expect(BalenaS3SourceBase.isESRVersion(v)).to.be.true; + }); }); it('should return true for unified ESR versions', function () {