vs-deploy
Version:
Commands for deploying files of your workspace to a destination.
1,319 lines • 82 kB
JavaScript
"use strict";
/// <reference types="node" />
Object.defineProperty(exports, "__esModule", { value: true });
// The MIT License (MIT)
//
// vs-deploy (https://github.com/mkloubert/vs-deploy)
// Copyright (c) Marcel Joachim Kloubert <marcel.kloubert@gmx.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
const ChildProcess = require("child_process");
const CompareVersion = require('compare-versions');
const deploy_contracts = require("./contracts");
const deploy_globals = require("./globals");
const deploy_values = require("./values");
const deploy_workspace = require("./workspace");
const FileType = require("file-type");
const FS = require("fs");
const FTP = require('jsftp');
const Glob = require('glob');
const HTTP = require("http");
const HTTPs = require("https");
const i18 = require("./i18");
const IsBinaryFile = require("isbinaryfile");
const MergeDeep = require('merge-deep');
const MIME = require('mime');
const Minimatch = require("minimatch");
const Moment = require("moment");
const Path = require("path");
const SFTP = require("ssh2-sftp-client");
const TMP = require("tmp");
const URL = require("url");
const vscode = require("vscode");
const Workflows = require("node-workflows");
let nextHtmlDocId = -1;
/**
* Applies values to an object.
*
* @param {T} obj The object to apply the values to.
* @param {deploy_contracts.ObjectWithNameAndValue|deploy_contracts.ObjectWithNameAndValue[]} values The values to apply.
* @param {boolean} [cloneObj] Clone object or not.
*
* @return {T} The object with the applied values.
*/
function applyValues(obj, values, cloneObj = true) {
values = asArray(values).filter(v => v);
if (toBooleanSafe(cloneObj)) {
obj = cloneObject(obj);
}
if (obj) {
let applyTo = cloneObject(obj.applyValuesTo);
if (applyTo) {
for (let p in applyTo) {
let valueToSet = applyTo[p];
if (values.length > 0) {
valueToSet = deploy_values.replaceWithValues(values, valueToSet);
}
obj[p] = valueToSet;
}
}
}
return obj;
}
exports.applyValues = applyValues;
/**
* Returns a value as array.
*
* @param {T | T[]} val The value.
*
* @return {T[]} The value as array.
*/
function asArray(val) {
if (!Array.isArray(val)) {
return [val];
}
return val;
}
exports.asArray = asArray;
/**
* Clones an object / value deep.
*
* @param {T} val The value / object to clone.
*
* @return {T} The cloned value / object.
*/
function cloneObject(val) {
if (!val) {
return val;
}
return JSON.parse(JSON.stringify(val));
}
exports.cloneObject = cloneObject;
/**
* Compares two values for a sort operation.
*
* @param {T} x The left value.
* @param {T} y The right value.
*
* @return {number} The "sort value".
*/
function compareValues(x, y) {
if (x === y) {
return 0;
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
return 0;
}
exports.compareValues = compareValues;
/**
* Compares values by using a selector.
*
* @param {T} x The left value.
* @param {T} y The right value.
* @param {Function} selector The selector.
*
* @return {number} The "sort value".
*/
function compareValuesBy(x, y, selector) {
if (!selector) {
selector = (t) => t;
}
return compareValues(selector(x), selector(y));
}
exports.compareValuesBy = compareValuesBy;
/**
* Compares two versions.
*
* @param {any} current The current value.
* @param {any} other The other value.
*
* @returns {number} The sort value.
*/
function compareVersions(current, other) {
if (!isNullOrUndefined(current)) {
current = toStringSafe(current).trim();
}
if (!isNullOrUndefined(other)) {
other = toStringSafe(other).trim();
}
return CompareVersion(current, other);
}
exports.compareVersions = compareVersions;
/**
* Creates a quick pick for deploying a single file.
*
* @param {string} file The file to deploy.
* @param {deploy_contracts.DeployTarget} target The target to deploy to.
* @param {number} index The zero based index.
* @param {deploy_values.ValueBase[]} [values] Values / placeholders to use.
*
* @returns {deploy_contracts.DeployFileQuickPickItem} The new item.
*/
function createFileQuickPick(file, target, index, values) {
let qp = createTargetQuickPick(target, index, values);
qp['file'] = file;
return qp;
}
exports.createFileQuickPick = createFileQuickPick;
/**
* Creates a quick pick for a package.
*
* @param {deploy_contracts.DeployPackage} pkg The package.
* @param {number} index The zero based index.
* @param {deploy_values.ValueBase[]} [values] Values / placeholders to use.
*
* @returns {deploy_contracts.DeployPackageQuickPickItem} The new item.
*/
function createPackageQuickPick(pkg, index, values) {
let name = toStringSafe(pkg.name).trim();
if ('' === name) {
name = i18.t('packages.defaultName', index + 1);
}
let description = toStringSafe(pkg.description).trim();
let item = {
description: description,
label: name,
package: pkg,
};
// item.detail
Object.defineProperty(item, 'detail', {
enumerable: true,
get: () => {
return deploy_values.replaceWithValues(values, pkg.detail);
}
});
return item;
}
exports.createPackageQuickPick = createPackageQuickPick;
/**
* Creates a simple 'completed' callback for a promise.
*
* @param {Function} resolve The 'succeeded' callback.
* @param {Function} reject The 'error' callback.
*
* @return {SimpleCompletedAction<TResult>} The created action.
*/
function createSimplePromiseCompletedAction(resolve, reject) {
return (err, result) => {
if (err) {
if (reject) {
reject(err);
}
}
else {
if (resolve) {
resolve(result);
}
}
};
}
exports.createSimplePromiseCompletedAction = createSimplePromiseCompletedAction;
/**
* Creates a quick pick for a target.
*
* @param {deploy_contracts.DeployTarget} target The target.
* @param {number} index The zero based index.
* @param {deploy_values.ValueBase[]} [values] Values / placeholders to use.
*
* @returns {deploy_contracts.DeployTargetQuickPickItem} The new item.
*/
function createTargetQuickPick(target, index, values) {
let name = toStringSafe(target.name).trim();
if (!name) {
name = i18.t('targets.defaultName', index + 1);
}
let description = toStringSafe(target.description).trim();
let item = {
description: description,
label: name,
target: target,
};
// item.detail
Object.defineProperty(item, 'detail', {
enumerable: true,
get: () => {
return deploy_values.replaceWithValues(values, target.detail);
}
});
return item;
}
exports.createTargetQuickPick = createTargetQuickPick;
/**
* Deploys files.
*
* @param {string | string[]} files The files to deploy.
* @param {deploy_contracts.DeployTargetList} targets The targets to deploy to.
* @param {symbol} [sym] The custom symbol to use for the identification.
*
* @return {Promise<deploy_contracts.DeployFilesEventArguments>} The promise.
*/
function deployFiles(files, targets, sym) {
return new Promise((resolve, reject) => {
let completed = (err, args) => {
if (err) {
reject(err);
}
else {
resolve(args);
}
};
try {
let alreadyInvoked = false;
let listener;
listener = function (args) {
if (alreadyInvoked) {
return;
}
if (!isNullOrUndefined(sym) && (sym !== args.symbol)) {
return;
}
alreadyInvoked = true;
try {
deploy_globals.EVENTS.removeListener(deploy_contracts.EVENT_DEPLOYFILES_COMPLETE, listener);
}
catch (e) {
log(i18.t('errors.withCategory', 'helpers.deployFiles()', e));
}
completed();
};
deploy_globals.EVENTS.on(deploy_contracts.EVENT_DEPLOYFILES_COMPLETE, listener);
deploy_globals.EVENTS.emit(deploy_contracts.EVENT_DEPLOYFILES, files, targets, sym);
}
catch (e) {
completed(e);
}
});
}
exports.deployFiles = deployFiles;
/**
* Tries to detect the MIME type of a file.
*
* @param {string} file The Filename.
* @param {any} defValue The default value.
*
* @return {string} The MIME type.
*/
function detectMimeByFilename(file, defValue = 'application/octet-stream') {
let mime;
try {
mime = MIME.lookup(file);
}
catch (e) {
log(i18.t('errors.withCategory', 'helpers.detectMimeByFilename()', e));
}
mime = normalizeString(mime);
if ('' === mime) {
mime = defValue;
}
return mime;
}
exports.detectMimeByFilename = detectMimeByFilename;
/**
* Checks if a file path does match by any pattern.
*
* @param {string} file The path to check.
* @param {deploy_contracts.FileFilter} filter The filter to use.
*
* @return {boolean} Does match or not.
*/
function doesFileMatchByFilter(file, filter) {
if (!filter) {
return null;
}
file = toStringSafe(file);
// files in include
let allFilePatterns = [];
if (filter.files) {
allFilePatterns = asArray(filter.files).map(x => toStringSafe(x))
.filter(x => '' !== x);
allFilePatterns = distinctArray(allFilePatterns);
}
if (allFilePatterns.length < 1) {
allFilePatterns.push('**'); // include all by default
}
// files to exclude
let allExcludePatterns = [];
if (filter.exclude) {
allExcludePatterns = asArray(filter.exclude).map(x => toStringSafe(x))
.filter(x => '' !== x);
}
allExcludePatterns = distinctArray(allExcludePatterns);
let doesPatternMatch = (pattern) => {
return Minimatch(file, pattern, {
dot: true,
nonegate: true,
nocomment: true,
});
};
// first check if ignored
while (allExcludePatterns.length > 0) {
let ep = allExcludePatterns.shift();
if (doesPatternMatch(ep)) {
return false; // ignored / excluded
}
}
// now check if matches
while (allFilePatterns.length > 0) {
let fp = allFilePatterns.shift();
if (doesPatternMatch(fp)) {
return true; // included / does match
}
}
return false;
}
exports.doesFileMatchByFilter = doesFileMatchByFilter;
/**
* Removes duplicate entries from an array.
*
* @param {T[]} arr The input array.
*
* @return {T[]} The filtered array.
*/
function distinctArray(arr) {
if (!arr) {
return arr;
}
return arr.filter((x, i) => {
return arr.indexOf(x) === i;
});
}
exports.distinctArray = distinctArray;
/**
* Filters "conditional" items.
*
* @param {T|T[]} items The items to filter.
* @param {deploy_values.ValueBase|deploy_values.ValueBase[]} [values] The values to use.
*
* @return {T[]} The filtered items.
*/
function filterConditionalItems(items, values) {
let result = asArray(items).filter(x => x);
result = result.filter((x, idx) => {
try {
let conditions = asArray(x.if).map(x => toStringSafe(x))
.filter(x => '' !== x.trim());
for (let i = 0; i < conditions.length; i++) {
let cv = new deploy_values.CodeValue({
code: conditions[i],
name: `condition_${idx}_${i}`,
type: 'code',
});
cv.otherValueProvider = () => asArray(values).filter(x => x);
if (!toBooleanSafe(cv.value)) {
return false; // at least one condition does NOT match
}
}
}
catch (e) {
log(i18.t('errors.withCategory', 'helpers.filterConditionalItems()', e));
return false;
}
return true;
});
return result;
}
exports.filterConditionalItems = filterConditionalItems;
/**
* Filters items by platform.
*
* @param {(T|T[])} items The items to filter.
* @param {string} [platform] The custom name of the platform to use.
*
* @returns {T[]} The new list of filtered items.
*/
function filterPlatformItems(items, platform) {
platform = normalizeString(platform);
if ('' === platform) {
platform = normalizeString(process.platform);
}
return asArray(items).filter(x => x)
.filter(x => {
let platformNames = asArray(x.platforms).map(x => normalizeString(x))
.filter(x => x);
return platformNames.length < 1 ||
platformNames.indexOf(platform) > -1;
});
}
exports.filterPlatformItems = filterPlatformItems;
/**
* Formats a string.
*
* @param {any} formatStr The value that represents the format string.
* @param {any[]} [args] The arguments for 'formatStr'.
*
* @return {string} The formated string.
*/
function format(formatStr, ...args) {
return formatArray(formatStr, args);
}
exports.format = format;
/**
* Formats a string.
*
* @param {any} formatStr The value that represents the format string.
* @param {any[]} [args] The arguments for 'formatStr'.
*
* @return {string} The formated string.
*/
function formatArray(formatStr, args) {
if (!args) {
args = [];
}
formatStr = toStringSafe(formatStr);
// apply arguments in
// placeholders
return formatStr.replace(/{(\d+)(\:)?([^}]*)}/g, (match, index, formatSeparator, formatExpr) => {
index = parseInt(toStringSafe(index).trim());
let resultValue = args[index];
if (':' === formatSeparator) {
// collect "format providers"
let formatProviders = toStringSafe(formatExpr).split(',')
.map(x => x.toLowerCase().trim())
.filter(x => x);
// transform argument by
// format providers
formatProviders.forEach(fp => {
switch (fp) {
case 'leading_space':
resultValue = toStringSafe(resultValue);
if ('' !== resultValue) {
resultValue = ' ' + resultValue;
}
break;
case 'lower':
resultValue = toStringSafe(resultValue).toLowerCase();
break;
case 'trim':
resultValue = toStringSafe(resultValue).trim();
break;
case 'upper':
resultValue = toStringSafe(resultValue).toUpperCase();
break;
case 'surround':
resultValue = toStringSafe(resultValue);
if ('' !== resultValue) {
resultValue = "'" + toStringSafe(resultValue) + "'";
}
break;
}
});
}
if ('undefined' === typeof resultValue) {
return match;
}
return toStringSafe(resultValue);
});
}
exports.formatArray = formatArray;
/**
* Returns the list of files by a filter that should be deployed.
*
* @param {deploy_contracts.FileFilter} filter The filter.
* @param {boolean} useGitIgnoreStylePatterns Also check directory patterns, like in .gitignore files, or not.
*
* @return {string[]} The list of files.
*/
function getFilesByFilter(filter, useGitIgnoreStylePatterns) {
if (!filter) {
return [];
}
useGitIgnoreStylePatterns = toBooleanSafe(useGitIgnoreStylePatterns);
// files in include
let allFilePatterns = [];
if (filter.files) {
allFilePatterns = asArray(filter.files).map(x => toStringSafe(x))
.filter(x => '' !== x);
allFilePatterns = distinctArray(allFilePatterns);
}
if (allFilePatterns.length < 1) {
allFilePatterns.push('**'); // include all by default
}
// files to exclude
let allExcludePatterns = [];
if (filter.exclude) {
allExcludePatterns = asArray(filter.exclude).map(x => toStringSafe(x))
.filter(x => '' !== x);
}
allExcludePatterns = distinctArray(allExcludePatterns);
// collect files to deploy
let filesToDeploy = [];
allFilePatterns.forEach(x => {
let matchingFiles = Glob.sync(x, {
absolute: true,
cwd: deploy_workspace.getRootPath(),
dot: true,
ignore: allExcludePatterns,
nodir: true,
root: deploy_workspace.getRootPath(),
});
matchingFiles.forEach(y => filesToDeploy.push(y));
});
return distinctArray(filesToDeploy);
}
exports.getFilesByFilter = getFilesByFilter;
/**
* Returns the list of files by a filter that should be deployed.
*
* @param {deploy_contracts.FileFilter} filter The filter.
* @param {boolean} useGitIgnoreStylePatterns Also check directory patterns, like in .gitignore files, or not.
*
* @return {Promise<string[]>} The promise.
*/
function getFilesByFilterAsync(filter, useGitIgnoreStylePatterns) {
useGitIgnoreStylePatterns = toBooleanSafe(useGitIgnoreStylePatterns);
// files in include
let allFilePatterns = [];
if (filter.files) {
allFilePatterns = asArray(filter.files).map(x => toStringSafe(x))
.filter(x => '' !== x);
allFilePatterns = distinctArray(allFilePatterns);
}
if (allFilePatterns.length < 1) {
allFilePatterns.push('**'); // include all by default
}
// files to exclude
let allExcludePatterns = [];
if (filter.exclude) {
allExcludePatterns = asArray(filter.exclude).map(x => toStringSafe(x))
.filter(x => '' !== x);
}
allExcludePatterns = distinctArray(allExcludePatterns);
return new Promise((resolve, reject) => {
let completed = (err, files) => {
if (err) {
reject(err);
}
else {
resolve(distinctArray(files || []));
}
};
if (filter) {
try {
let wf = Workflows.create();
wf.next((ctx) => {
ctx.result = [];
});
allFilePatterns.forEach(x => {
wf.next((ctx) => {
let files = ctx.result;
return new Promise((res, rej) => {
try {
Glob(x, {
absolute: true,
cwd: deploy_workspace.getRootPath(),
dot: true,
ignore: allExcludePatterns,
nodir: true,
root: deploy_workspace.getRootPath(),
}, (err, matchingFiles) => {
if (err) {
rej(err);
}
else {
ctx.result = files.concat(matchingFiles);
res();
}
});
}
catch (e) {
rej(e);
}
});
});
});
wf.start().then((files) => {
completed(null, files);
}).catch((err) => {
completed(err);
});
}
catch (e) {
completed(e);
}
}
else {
completed(null);
}
});
}
exports.getFilesByFilterAsync = getFilesByFilterAsync;
/**
* Returns the list of files of a package that should be deployed.
*
* @param {deploy_contracts.DeployPackage} pkg The package.
* @param {boolean} useGitIgnoreStylePatterns Also check directory patterns, like in .gitignore files, or not.
*
* @return {string[]} The list of files.
*/
function getFilesOfPackage(pkg, useGitIgnoreStylePatterns) {
pkg = cloneObject(pkg);
if (pkg) {
if (!pkg.exclude) {
pkg.exclude = [];
}
if (toBooleanSafe(pkg.noNodeModules)) {
pkg.exclude.push('node_modules/**');
}
}
return getFilesByFilter(pkg, useGitIgnoreStylePatterns);
}
exports.getFilesOfPackage = getFilesOfPackage;
/**
* Loads the body from a HTTP response.
*
* @param {HTTP.IncomingMessage} resp The response.
*
* @return {Promise<Buffer>} The promise.
*/
function getHttpBody(resp) {
return new Promise((resolve, reject) => {
let body;
let completed = (err) => {
if (err) {
reject(err);
}
else {
resolve(body);
}
};
if (!resp) {
completed();
return;
}
body = Buffer.alloc(0);
try {
let appendChunk = (chunk) => {
try {
if (chunk) {
body = Buffer.concat([body, chunk]);
}
return true;
}
catch (e) {
completed(e);
return false;
}
};
resp.on('data', (chunk) => {
if (!appendChunk(chunk)) {
return;
}
});
resp.on('end', (chunk) => {
if (!appendChunk(chunk)) {
return;
}
let l = body.length;
completed();
});
resp.on('error', (err) => {
completed(err);
});
}
catch (e) {
completed(e);
}
});
}
exports.getHttpBody = getHttpBody;
/**
* Returns the sort value from a sortable.
*
* @param {deploy_contracts.Sortable} s The sortable object.
* @param {deploy_contracts.ValueProvider<string>} [nameProvider] The custom function that provides the name of the machine.
*
* @return {any} The sort value.
*/
function getSortValue(s, nameProvider) {
let name;
if (nameProvider) {
name = normalizeString(nameProvider());
}
let sortValue = s.sortOrder;
if (!sortValue) {
sortValue = 0;
}
if ('number' !== typeof sortValue) {
// handle as object and find a property
// that has the same name as this machine
let sortObj = sortValue;
let valueAlreadySet = false;
Object.getOwnPropertyNames(sortObj).forEach(p => {
if (!valueAlreadySet && !normalizeString(p)) {
sortValue = sortObj[p]; // custom default value defined
}
if (normalizeString(p) == name) {
sortValue = sortObj[p]; // found
valueAlreadySet = true;
}
});
}
// keep sure to have a number here
sortValue = parseFloat(('' + sortValue).trim());
if (isNaN(sortValue)) {
sortValue = 0;
}
return sortValue;
}
exports.getSortValue = getSortValue;
/**
* Returns the color for a status bar item based an operation result.
*
* @param {any} err The error.
* @param {number} succeedCount The number of successed operations.
* @param {number} failedCount The number of failed operations.
*
* @return {string} The color.
*/
function getStatusBarItemColor(err, succeedCount, failedCount) {
let color;
if (err || failedCount > 0) {
if (succeedCount < 1) {
color = '#ff0000';
}
else {
color = '#ffff00';
}
}
return color;
}
exports.getStatusBarItemColor = getStatusBarItemColor;
/**
* Returns the value from a "parameter" object.
*
* @param {Object} params The object.
* @param {string} name The name of the parameter.
*
* @return {string} The value of the parameter (if found).
*/
function getUrlParam(params, name) {
if (params) {
name = normalizeString(name);
for (let p in params) {
if (normalizeString(p) === name) {
return toStringSafe(params[p]);
}
}
}
}
exports.getUrlParam = getUrlParam;
/**
* Checks if data is binary or text content.
*
* @param {Buffer} data The data to check.
*
* @returns {Promise<boolean>} The promise.
*/
function isBinaryContent(data) {
return new Promise((resolve, reject) => {
let completed = createSimplePromiseCompletedAction(resolve, reject);
if (!data) {
completed(null);
return;
}
try {
IsBinaryFile(data, data.length, (err, result) => {
if (err) {
completed(err);
return;
}
completed(null, toBooleanSafe(result));
});
}
catch (e) {
completed(e);
}
});
}
exports.isBinaryContent = isBinaryContent;
/**
* Checks if the string representation of a value is empty
* or contains whitespaces only.
*
* @param {any} val The value to check.
*
* @return {boolean} Is empty or not.
*/
function isEmptyString(val) {
return '' === toStringSafe(val).trim();
}
exports.isEmptyString = isEmptyString;
/**
* Checks if a file (or directory) path is ignored.
*
* @param {string} fileOrDir The file / directory to check.
* @param {string|string[]} patterns One or more (glob) pattern to use.
* @param {boolean} useGitIgnoreStylePatterns Also check directory patterns, like in .gitignore files, or not.
* @param {boolean} {fastCheck} Use 'minimatch' instead of 'node.glob'.
*
* @return {boolean} Is ignored or not.
*/
function isFileIgnored(file, patterns, useGitIgnoreStylePatterns, fastCheck) {
useGitIgnoreStylePatterns = toBooleanSafe(useGitIgnoreStylePatterns);
file = toStringSafe(file);
if ('' === file.trim()) {
return true;
}
if (!Path.isAbsolute(file)) {
file = Path.join(deploy_workspace.getRootPath(), file);
}
file = Path.resolve(file);
file = replaceAllStrings(file, Path.sep, '/');
patterns = asArray(patterns).map(p => toStringSafe(p))
.filter(p => '' !== p.trim());
patterns = distinctArray(patterns);
fastCheck = toBooleanSafe(fastCheck);
while (patterns.length > 0) {
let p = patterns.shift();
let isMatching = false;
if (fastCheck) {
// use minimatch
isMatching = Minimatch(file, p, {
dot: true,
nonegate: true,
nocomment: true,
});
}
else {
let matchingFiles = Glob.sync(p, {
absolute: true,
cwd: deploy_workspace.getRootPath(),
dot: true,
nodir: true,
root: deploy_workspace.getRootPath(),
});
isMatching = matchingFiles.indexOf(file) > -1;
}
if (isMatching) {
return true;
}
}
return false;
}
exports.isFileIgnored = isFileIgnored;
/**
* Checks if a value is (null) or (undefined).
*
* @param {any} val The value to check.
*
* @return {boolean} Is (null)/(undefined) or not.
*/
function isNullOrUndefined(val) {
return null === val ||
'undefined' === typeof val;
}
exports.isNullOrUndefined = isNullOrUndefined;
/**
* Checks if a value is (null), (undefined) or an empty string.
*
* @param {any} val The value to check.
*
* @return {boolean} Is (null)/(undefined)/empty string or not.
*/
function isNullUndefinedOrEmptyString(val) {
return isNullOrUndefined(val) ||
'' === toStringSafe(val);
}
exports.isNullUndefinedOrEmptyString = isNullUndefinedOrEmptyString;
/**
* Loads base settings for object from files.
*
* @param {T|T[]} objs The objects.
* @param {deploy_values.ValueBase|deploy_values.ValueBase[]} values The values to use for the file path(s).
* @param {boolean} cloneObjects Clone objects or not.
*
* @return {T[]} The new list.
*/
function loadBaseSettingsFromFiles(objs, values, cloneObjects = true) {
return asArray(objs).filter(x => x).map(x => {
let loadFrom;
try {
loadFrom = deploy_values.replaceWithValues(values, x.loadFrom);
if (!isEmptyString(x.loadFrom)) {
if (!Path.isAbsolute(loadFrom)) {
loadFrom = Path.join(deploy_workspace.getRootPath(), '.vscode', loadFrom);
}
let basePackages = JSON.parse(FS.readFileSync(loadFrom).toString('utf8'));
basePackages = loadBaseSettingsFromFiles(basePackages, values);
let args = [{}, x].concat(basePackages);
x = MergeDeep.apply(null, [{}, x].concat(basePackages));
}
if (toBooleanSafe(cloneObjects)) {
x = cloneObject(x);
}
delete x['loadFrom'];
}
catch (e) {
vscode.window.showErrorMessage(i18.t('load.from.failed', loadFrom, e)).then(() => { }, (err) => {
log(`[ERROR] helpers.loadBaseSettingsFromFiles(): ${e}`);
});
}
return x;
});
}
exports.loadBaseSettingsFromFiles = loadBaseSettingsFromFiles;
/**
* Loads a "data transformer" module.
*
* @param {string} file The path of the module's file.
* @param {boolean} useCache Use cache or not.
*
* @return {deploy_contracts.DataTransformModule} The loaded module.
*/
function loadDataTransformerModule(file, useCache = false) {
return loadModule(file, useCache);
}
exports.loadDataTransformerModule = loadDataTransformerModule;
/**
* Loads a module for a deploy operation.
*
* @param {string} file The path of the module's file.
* @param {boolean} useCache Use cache or not.
*
* @return {deploy_contracts.DeployScriptOperationModule} The loaded module.
*/
function loadDeployScriptOperationModule(file, useCache = false) {
return loadModule(file, useCache);
}
exports.loadDeployScriptOperationModule = loadDeployScriptOperationModule;
/**
* Loads data from a source.
*
* @param {string} src The path or URL to the source.
*
* @return {Promise<DownloadResult>} The promise.
*/
function loadFrom(src) {
return new Promise((resolve, reject) => {
let completedInvoked = false;
let completed = (err, result) => {
if (completedInvoked) {
return;
}
completedInvoked = true;
if (err) {
reject(err);
}
else {
resolve(result);
}
};
try {
src = toStringSafe(src);
let url;
try {
url = URL.parse(src);
}
catch (e) {
url = null;
}
let wf = Workflows.create();
let isLocal = true;
if (url) {
isLocal = false;
let fileName = Path.basename(url.path);
let protocol = normalizeString(url.protocol);
let getUserAndPassword = () => {
let user;
let pwd;
if (!isNullUndefinedOrEmptyString(url.auth)) {
let auth = url.auth;
if (auth.indexOf(':') > -1) {
let parts = auth.split(':');
user = parts[0];
if ('' === user) {
user = undefined;
}
pwd = parts.filter((x, i) => i > 0).join(':');
if ('' === pwd) {
pwd = undefined;
}
}
else {
user = auth;
}
}
return {
password: pwd,
user: user,
};
};
switch (protocol) {
case 'ftp:':
// FTP server
{
wf.next((ctx) => {
return new Promise((res, rej) => {
try {
let port = 21;
if (!isNullUndefinedOrEmptyString(url.port)) {
port = parseInt(toStringSafe(url.port).trim());
}
// authorization
let auth = getUserAndPassword();
// open connection
let conn = new FTP({
host: url.hostname,
port: port,
user: auth.user,
pass: auth.password,
});
conn.get(url.path, (err, socket) => {
if (err) {
rej(err); // could not get file from FTP
}
else {
try {
let result = Buffer.alloc(0);
socket.on("data", function (data) {
try {
if (data) {
result = Buffer.concat([result, data]);
}
}
catch (e) {
rej(e);
}
});
socket.once("close", function (hadErr) {
if (hadErr) {
rej(new Error('FTP error!'));
}
else {
res({
data: result,
fileName: fileName,
});
}
});
socket.resume();
}
catch (e) {
rej(e); // socket error
}
}
});
}
catch (e) {
rej(e); // global FTP error
}
});
});
}
break;
case 'sftp:':
// SFTP server
{
// start connection
wf.next((ctx) => {
return new Promise((res, rej) => {
try {
let port = 22;
if (!isNullUndefinedOrEmptyString(url.port)) {
port = parseInt(toStringSafe(url.port).trim());
}
// authorization
let auth = getUserAndPassword();
let conn = new SFTP();
conn.connect({
host: url.hostname,
port: port,
username: auth.user,
password: auth.password,
}).then(() => {
res(conn);
}).catch((err) => {
rej(err);
});
}
catch (e) {
rej(e);
}
});
});
// start reading file
wf.next((ctx) => {
let conn = ctx.previousValue;
return new Promise((res, rej) => {
conn.get(url.path).then((stream) => {
try {
res(stream);
}
catch (e) {
rej(e);
}
}).catch((err) => {
rej(err);
});
});
});
// create temp file
wf.next((ctx) => {
let stream = ctx.previousValue;
return new Promise((res, rej) => {
TMP.tmpName({
keep: true,
}, (err, tempFile) => {
if (err) {
rej(err);
}
else {
res({
deleteTempFile: () => {
return new Promise((res2, rej2) => {
FS.exists(tempFile, (exists) => {
if (exists) {
FS.unlink(tempFile, (err) => {
if (err) {
rej2(err);
}
else {
res2();
}
});
}
else {
res2();
}
});
});
},
tempFile: tempFile,
stream: stream,
});
}
});
});
});
// write to temp file
wf.next((ctx) => {
let deleteTempFile = ctx.previousValue.deleteTempFile;
let stream = ctx.previousValue.stream;
let tempFile = ctx.previousValue.tempFile;
return new Promise((res, rej) => {
let downloadCompleted = (err) => {
if (err) {
deleteTempFile().then(() => {
rej(err);
}).catch((e) => {
//TODO: log
rej(err);
});
}
else {
res({
deleteTempFile: deleteTempFile,
tempFile: tempFile,
});
}
};
try {
stream.once('error', (err) => {
if (err) {
downloadCompleted(err);
}
});
let pipe = stream.pipe(FS.createWriteStream(tempFile));
pipe.once('error', (err) => {
;
if (err) {
downloadCompleted(err);
}
});
stream.once('end', () => {
downloadCompleted(null);
});
}
catch (e) {
downloadCompleted(e);
}
});
});
wf.next((ctx) => {
let deleteTempFile = ctx.previousValue.deleteTempFile;
let tempFile = ctx.previousValue.tempFile;
return new Promise((res, rej) => {
let readCompleted = (err, d) => {
if (err) {
rej(err);
}
else {
res({
data: d,
fileName: fileName,
});
}
};
FS.readFile(tempFile, (err, data) => {
deleteTempFile().then(() => {
readCompleted(err, data);
}).catch((e) => {
//TODO: log
readCompleted(err, data);
});
});
});
});
}
break;
case 'http:':
case 'https:':
// web resource
{
wf.next((ctx) => {
return new Promise((resolve, reject) => {
try {
let requestOpts = {
hostname: url.hostname,
path: url.path,
method: 'GET',
};
let setHeader = (name, value) => {
if (isNullOrUndefined(requestOpts.headers)) {
requestOpts.headers = {};
}
requestOpts.headers[name] = value;
};
// authorization?
if (!isNullUndefinedOrEmptyString(url.auth)) {
let b64Auth = (new Buffer(toStringSafe(url.auth))).toString('base64');
setHeader('Authorization', 'Basic ' + b64Auth);
}
let requestHandler = (resp) => {
let mime;
if (resp.headers) {
for (let h in resp.headers) {
if ('content-type' === normalizeString(h)) {
mime = normalizeString(resp.headers[h]);
break;
}
}
}
readHttpBody(resp).then((data) => {
resolve({
data: data,
fileName: fileName,
mime: mime,
});
}).catch((err) => {