src/index.ts
'use strict';
import when from 'when';
import { Files, ReplaceNativeSeparator, Run } from './utility.js';
import { createSfx } from './createSfx.js';
import { isWindows } from 'node-sys';
const onProgress = (firstChar: string) =>
function(data: string): string[] {
/**
* When a stdout is emitted, parse each line and search for a pattern. When
* the pattern is found, extract the file (or directory) name from it and
* pass it to an array. Finally returns this array.
*/
return data.split('\n')
.filter((line) => line.startsWith(firstChar))
.map((line) => ReplaceNativeSeparator(line.slice(2)));
}
function retry(
command: string | undefined,
options: {},
override: boolean | undefined,
progress: (arg0: any) => void,
onprogress: (data: any) => any[],
resolve: (arg0: any) => void,
reject: (arg0: any) => void,
archive: string
) {
// Start the command
const executables = ['7z', '7za']; // Two or more items
let position = 0;
const runner = () => Run(executables[position], command, options, override)
.progress((data: any) => progress(onprogress(data)))
.then((args: string[]) => resolve(args)) // When all is done resolve the Promise.
.catch((err: any) => { // Catch the error and pass it to the reject function of the Promise.
if (position === executables.length - 1) return reject(err);
console.error(archive + ' failed using `' + executables[position] +
'`, retrying with `' + executables[position + 1] + '`.');
position++;
runner();
});
return runner();
}
/**
* Create/add content to an archive.
*
* @param filepath {string} Path to the archive.
* @param files {string|array} Files to add.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const createArchive =
(SevenZip.createArchive =
SevenZip.add =
function (
filepath: string,
files: string | string[],
options: any,
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('+');
// Convert array of files into a string if needed.
files = Files(files);
// Create a string that can be parsed by `run`.
let command = 'a "' + filepath + '" ' + files;
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'CreateArchive'
);
});
});
/**
* Delete content from an archive.
*
* @param filepath {string} Path to the archive.
* @param files {string|array} Files to remove.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const deleteArchive =
(SevenZip.deleteArchive =
SevenZip.delete =
function (
filepath: string,
files: string | string[],
options: { files?: string[] | undefined } | undefined,
override = false
) {
return new Promise<string[]>(function (resolve, reject) {
// Convert array of files into a string if needed.
files = Files(files);
// Create a string that can be parsed by `run`.
let command = `d "${filepath}" ${files}`;
// Start the command
const executables = ['7z', '7za']; // Two or more items
let position = 0;
const runner = () => Run(executables[position], command, options, override)
.then((args: any[]) => resolve(args)) // When all is done resolve the Promise.
.catch((err: any) => { // Catch the error and pass it to the reject function of the Promise.
if (position === executables.length - 1) return reject(err);
console.error('DeleteArchive failed using `' + executables[position] +
'`, retrying with `' + executables[position + 1] + '`.');
position++;
runner();
});
return runner();
});
});
/**
* Extract an archive.
*
* @param {string} archive Path to the archive.
* @param {string} dest Destination.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Extracted files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const extractArchive =
(SevenZip.extractArchive =
SevenZip.extract =
function (
filepath: string,
dest = '*',
options = {},
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('-');
// Create a string that can be parsed by `run`.
let command = 'e "' + filepath + '" -o"' + dest + '" ';
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'ExtractArchive'
);
});
});
/**
* Extract an archive with full paths.
*
* @param filepath {string} Path to the archive.
* @param dest {string} Destination.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Extracted files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const fullArchive =
(SevenZip.fullArchive =
SevenZip.extractFull =
function (
filepath: string,
dest = '*',
options = {},
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('-');
// Create a string that can be parsed by `run`.
let command = 'x "' + filepath + '" -o"' + dest + '" ';
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'FullArchive'
);
});
});
/**
* List contents of archive.
*
* @param filepath {string} Path to the archive.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @progress {array} Listed files and directories.
* @resolve {Object} Tech spec about the archive.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const listArchive =
(SevenZip.listArchive =
SevenZip.list =
function (
filepath: string,
options: { files?: string[] | undefined } | undefined,
override = false
) {
return when.promise<Record<string, any>>(function (
resolve: (spec: Record<string, any>) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
let spec: Record<string, any> = {};
/* jshint maxlen: 130 */
let regex =
/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ([\.D][\.R][\.H][\.S][\.A]) +(\d+) +(\d+)? +(.+)/;
/* jshint maxlen: 80 */
let buffer = ''; //Store incomplete line of a progress data.
/**
* When a stdout is emitted, parse each line and search for a pattern.When
* the pattern is found, extract the file (or directory) name from it and
* pass it to an array. Finally returns this array.
*/
function onprogress(data: string) {
type Entry = {
date: Date;
attr: string;
size: number;
name: string;
};
let entries: Entry[] = [];
if (buffer.length > 0) {
data = buffer + data;
buffer = '';
}
// Populate the tech specs of the archive that are passed to the
// resolve handler.
type Param = [string, string, Function | null];
const params: Param[] = [
['Path = ', 'path', null],
['Type = ', 'type', null],
['Method = ', 'method', null],
['Physical Size = ', 'physicalSize', parseInt],
['Headers Size = ', 'headersSize', parseInt],
];
const lines = data.split('\n');
for (const line of lines) {
let lineDone = false;
for (const [beginning, paramType, fn] of params) {
if (line.startsWith(beginning)) {
const paramValue = line.slice(beginning.length);
spec[paramType] = fn ? fn(paramValue) : paramValue;
lineDone = true;
break;
}
}
if (lineDone) continue;
// Parse the stdout to find entries
let res = regex.exec(line);
if (res) {
let e = {
date: new Date(res[1]),
attr: res[2],
size: parseInt(res[3], 10),
name: ReplaceNativeSeparator(res[5]),
};
entries.push(e);
} // Line may be incomplete, Save it to the buffer.
else buffer = line;
}
return entries;
}
// Create a string that can be parsed by `run`.
let command = 'l "' + filepath + '" ';
// Start the command
const executables = isWindows() ? ['7z', '7za'] : ['7za'];
let position = 0;
const runner = () => Run(executables[position], command, options, override)
.progress((data: string) => progress(onprogress(data)))
.then((args: any) => resolve(position === 0 ? spec : args))
.catch((err: any) => {
if (position === executables.length - 1) return reject(err);
console.error('ListArchive failed using `' + executables[position] +
'`, retrying with `' + executables[position + 1] + '`.');
position++;
runner();
});
return runner();
});
});
/**
* Extract only selected files from archive.
*
* @param {string} filepath Path to the archive.
* @param {string} dest Destination.
* @param {string|array} files Files in archive to extract.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Extracted files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const onlyArchive =
(SevenZip.onlyArchive =
SevenZip.only =
function (
filepath: string,
dest: string,
files: any,
options = {},
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
options = Object.assign(options, {
files: files,
});
const onprogress = onProgress('-');
// Create a string that can be parsed by `run`.
let command = 'e "' + filepath + '" -o"' + dest + '"';
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'OnlyArchive'
);
});
});
/**
* Renames files in archive.
*
* @param filepath {string} Path to the archive.
* @param files {string} Files pairs to rename in archive.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const renameArchive =
(SevenZip.renameArchive =
SevenZip.rename =
function (
filepath: string,
files: string | string[],
options: {},
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('U');
// Convert array of files into a string if needed.
files = Files(files);
// Create a string that can be parsed by `run`.
let command = 'rn "' + filepath + '" ' + files;
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'RenameArchive'
);
});
});
/**
* Test integrity of archive.
*
* @param filepath {string} Path to the archive.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Extracted files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const testArchive =
(SevenZip.testArchive =
SevenZip.test =
function (filepath: string, options: {}, override = false) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('T');
// Create a string that can be parsed by `run`.
let command = 't "' + filepath + '"';
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'TestArchive'
);
});
});
/**
* Update content to an archive.
*
* @param filepath {string} Path to the archive.
* @param files {string} Files to update.
* @param options {Object} An object of acceptable 7-zip switch options.
* @param override {boolean} should binary directory change?
*
* @resolve {array} Arguments passed to the child-process.
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const updateArchive =
(SevenZip.updateArchive =
SevenZip.update =
function (
filepath: string,
files: string | string[],
options: {},
override = false
) {
return when.promise<string[]>(function (
resolve: (args: string[]) => void,
reject: (reason: any) => void,
progress: (arg0: any) => any
) {
const onprogress = onProgress('U');
// Convert array of files into a string if needed.
files = Files(files);
// Create a string that can be parsed by `run`.
let command = 'u "' + filepath + '" ' + files;
// Start the command
return retry(
command,
options,
override,
progress,
onprogress,
resolve,
reject,
'UpdateArchive'
);
});
});
/**
* Creates Windows self extracting archive, an Installation Package.
*
* @param {String} name Application name.
* @param {Array} files Files to add.
* @param {String} destination Application root for the `SfxPackages` directory, will default to package root.
* - All Sfx package archives are stored in the **created** `SfxPackages` directory.
* - The `destination` directory must already exists.
* @param {Object} options Object for Installer config and 7-zip switch options.
*
* `{`
*
* `title:` - Window title message, Default "`name` installation package created on `Current running platform OS`"
*
* `beginPrompt:` - Begin Prompt message, Default "Do you want to install `name`?""
*
* `progress:` - Value can be "yes" or "no". Default value is "yes".
*
* `runProgram:` - Command for executing. Default value is "setup.exe".
* Substring `% % T` will be replaced with path to temporary folder,
* where files were extracted
*
* `directory:` - Directory prefix for `RunProgram`. Default value is `.\`
*
* `executeFile:` Name of file for executing
*
* `executeParameters:` Parameters for `ExecuteFile`
*
* `}`
*
* `NOTE:` There are two ways to run program: `RunProgram` and `ExecuteFile`.
* - Use `RunProgram`, if you want to run some program from .7z archive.
* - Use `ExecuteFile`, if you want to open some document from .7z archive or
* if you want to execute some command from Windows.
* @param {String} type Application type `gui` or `console`. Default `gui`. Only `console` possible on **Linux** and **Mac** OS.
*
* @resolve {string} full filepath
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const createSfxWindows = (SevenZip.windowsSfx = function (
name: string,
files: string[],
destination: string | undefined,
options: Record<string, any> | undefined,
type: string | undefined
) {
return createSfx(name, files, destination, options, type, 'win32', '.exe');
});
/**
* Creates Linux self extracting archive.
*
* @param {String} name Application name.
* @param {Array} files Files to add.
* @param {String} destination Application root for the `SfxPackages` directory, will default to package root.
* - All Sfx package archives are stored in the **created** `SfxPackages` directory.
* - The `destination` directory must already exists.
* @param {Object} options Object for 7-zip switch options.
*
* @resolve {string} full filepath
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const createSfxLinux = (SevenZip.linuxSfx = function (
name: string,
files: string[],
destination: string | undefined,
options: Record<string, any> | undefined
) {
return createSfx(
name,
files,
destination,
options,
'console',
'linux',
'.elf'
);
});
/**
* Creates Apple macOS self extracting archive.
*
* @param {String} name Application name.
* @param {Array} files Files to add.
* @param {String} destination Application root for the `SfxPackages` directory, will default to package root.
* - All Sfx package archives are stored in the **created** `SfxPackages` directory.
* - The `destination` directory must already exists.
* @param {Object} options Object for 7-zip switch options.
*
* @resolve {string} full filepath
* @progress {array} Listed files and directories.
* @reject {Error} The error as issued by 7-Zip.
*
* @returns {Promise} Promise
*/
export const createSfxMac = (SevenZip.macSfx = function (
name: string,
files: string[],
destination: string | undefined,
options: Record<string, any> | undefined
) {
return createSfx(
name,
files,
destination,
options,
'console',
'darwin',
'.pkg'
);
});
function SevenZip() {}
export default SevenZip;
export const Zip = SevenZip;