@hkvstore/taco-cli
Version:
taco-cli is a command-line interface for rapid Apache Cordova development (forked from Microsoft taco-cli)
293 lines (287 loc) • 13.6 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/// <reference path="../../../typings/adm-zip.d.ts"/>
/// <reference path="../../../typings/replace.d.ts" />
/// <reference path="../../../typings/resourceManager.d.ts"/>
/// <reference path="../../../typings/tacoUtils.d.ts"/>
/// <reference path="../../../typings/tacoKits.d.ts"/>
/// <reference path="../../../typings/wrench.d.ts" />
;
var AdmZip = require("adm-zip");
var childProcess = require("child_process");
var crypto = require("crypto");
var fs = require("fs");
var os = require("os");
var path = require("path");
var Q = require("q");
var replace = require("replace");
var rimraf = require("rimraf");
var wrench = require("wrench");
var resources = require("../../resources/resourceManager");
var TacoErrorCodes = require("../tacoErrorCodes");
var errorHelper = require("../tacoErrorHelper");
var tacoUtility = require("taco-utils");
var CordovaWrapper = tacoUtility.CordovaWrapper;
var logger = tacoUtility.Logger;
var loggerHelper = tacoUtility.LoggerHelper;
var utils = tacoUtility.UtilHelper;
var TemplateDescriptor = (function () {
function TemplateDescriptor(templateOverrideInfo) {
this.id = templateOverrideInfo.templateId;
this.name = templateOverrideInfo.templateInfo.name;
}
TemplateDescriptor.prototype.getDescription = function () {
return resources.getString(this.name);
};
return TemplateDescriptor;
}());
var TemplateManager = (function () {
function TemplateManager(kits, tempTemplateDir) {
if (tempTemplateDir === void 0) { tempTemplateDir = os.tmpdir(); }
this.kitHelper = null;
this.templateName = null;
this.kitHelper = kits;
TemplateManager.temporaryTemplateDir = tempTemplateDir;
}
TemplateManager.performTokenReplacements = function (projectPath, appId, appName) {
var replaceParams = {
regex: "",
replacement: "",
paths: [path.resolve(projectPath)],
recursive: true,
silent: true
};
var tokens = {
"\\$appid\\$": appId,
"\\$projectname\\$": appName
};
Object.keys(tokens).forEach(function (token) {
replaceParams.regex = token;
replaceParams.replacement = tokens[token];
replace(replaceParams);
});
return Q.resolve(null);
};
TemplateManager.cleanTemporaryTemplateFolder = function (folderToDelete) {
if (path.basename(folderToDelete).indexOf(TemplateManager.TEMPORARY_TEMPLATE_PREFIX) === -1) {
// The requested folder does not start with the TACO template prefix, this may not be a TACO template so don't delete it
return Q.resolve({});
}
// Attempt to clean the temporary template folder
var deferred = Q.defer();
rimraf(folderToDelete, function (err) {
// This is best effort, resolve the promise whether there was an error or not
deferred.resolve({});
});
return deferred.promise;
};
TemplateManager.deleteIgnoredFiles = function (tempTemplatePath) {
wrench.readdirSyncRecursive(tempTemplatePath).forEach(function (item) {
var itemPath = path.join(tempTemplatePath, item);
// If the item no longer exists, it means it was deleted as part of a directory we ignored, so don't do anything
if (fs.existsSync(itemPath)) {
var ignoreSource = fs.statSync(itemPath).isDirectory() ? TemplateManager.IGNORE_DIRS_LIST : TemplateManager.IGNORE_FILES_LIST;
var itemName = path.basename(item);
if (ignoreSource.indexOf(itemName) !== -1) {
// This item is in the list of our items to ignore; delete it
rimraf.sync(itemPath);
}
}
});
};
TemplateManager.copyTemplateItemsToProject = function (cordovaParameters) {
/*
Cordova's --copy-from behavior: "cordova create [project path] --copy-from [template path]" supports 2 scenarios: 1) The template is for the entire project; 2) The template is only
for the www assets.
Cordova looks for a "www" folder at the root of the specified path ("the template"). If it finds one, then it assumes we are in case 1), otherwise it assumes we are in case 2).
For case 1), Cordova looks for 4 specific items at the root of the template: "config.xml", "www\", "merges\" and "hooks\". When it finds one of those items, Cordova copies it to the
user's project, otherwise it uses the default for that particular item. It ignores everything else in the template.
For case 2), Cordova copies everything it finds in the template over to the "www\" folder of the user's project, and uses default items for everything else ("config.xml", "merges\",
etc).
What this means for TACO project creation: If we are in case 1), we need to copy the template items that Cordova ignored. For simplicity, we will simply recursively copy the entire
template folder to the user's project, while using the "clobber = false" option of the NCP package. That way, all the items get copied over, but those that were already copied by
Cordova are ignored. If we are in case 2), it means we don't have anything to do, because Cordova already copied all the template items to the "www\" folder of the user's project.
*/
// If we are in case 2) (see above comment), we need to skip the copy step
var skipCopy = !fs.readdirSync(cordovaParameters.copyFrom).some(function (itemPath) {
return path.basename(itemPath) === "www";
});
if (skipCopy) {
return Q.resolve({});
}
var options = { clobber: false };
return utils.copyRecursive(cordovaParameters.copyFrom, cordovaParameters.projectPath, options);
};
TemplateManager.cordovaCreate = function (templatePath, cliVersion, cordovaParameters) {
cordovaParameters.copyFrom = templatePath;
return CordovaWrapper.create(cliVersion, cordovaParameters);
};
TemplateManager.createUnusedFolderPath = function () {
var dirPath = path.join(TemplateManager.temporaryTemplateDir, TemplateManager.TEMPORARY_TEMPLATE_PREFIX + crypto.pseudoRandomBytes(20).toString("hex"));
while (fs.existsSync(dirPath)) {
dirPath = path.join(TemplateManager.temporaryTemplateDir, TemplateManager.TEMPORARY_TEMPLATE_PREFIX + crypto.pseudoRandomBytes(20).toString("hex"));
}
return dirPath;
};
TemplateManager.gitClone = function (repo) {
var deferred = Q.defer();
var destination = TemplateManager.createUnusedFolderPath();
var command = "git";
var args = [
"clone",
"--depth",
"1",
repo,
destination
];
var options = { cwd: TemplateManager.temporaryTemplateDir, stdio: "inherit" }; // Set cwd for the git child process to be in the temporary dir to ensure any logs or other files get created there
childProcess.spawn(command, args, options)
.on("error", function (err) {
loggerHelper.logSeparatorLine();
if (err.code === "ENOENT") {
// ENOENT error thrown if no git is found
deferred.reject(errorHelper.get(TacoErrorCodes.CommandCreateNoGit));
}
else {
deferred.reject(err);
}
})
.on("exit", function (code) {
loggerHelper.logSeparatorLine();
if (code) {
deferred.reject(errorHelper.get(TacoErrorCodes.CommandCreateGitCloneError));
}
else {
deferred.resolve(destination);
}
});
return deferred.promise;
};
TemplateManager.acquireFromGit = function (templateUrl) {
loggerHelper.logSeparatorLine();
logger.log(resources.getString("CommandCreateGitTemplateHeader"));
return TemplateManager.gitClone(templateUrl);
};
/**
* Creates a kit project using 'cordova create' with the specified template.
*
* @param {string} The id of the desired kit
* @param {string} The id of the desired template
* @param {string} The version of the desired cordova CLI
* @param {ICordovaCreateParameters} The cordova parameters for the create command
*
* @return {Q.Promise<string>} A Q promise that is resolved with the template's display name if there are no errors
*/
TemplateManager.prototype.createKitProjectWithTemplate = function (kitId, templateId, cordovaCliVersion, cordovaParameters) {
var self = this;
templateId = templateId ? templateId : TemplateManager.DEFAULT_TEMPLATE_ID;
return this.acquireTemplate(templateId, kitId)
.then(function (templatePath) {
TemplateManager.deleteIgnoredFiles(templatePath);
return templatePath;
})
.then(function (templatePath) {
return TemplateManager.cordovaCreate(templatePath, cordovaCliVersion, cordovaParameters);
})
.then(function () {
return TemplateManager.copyTemplateItemsToProject(cordovaParameters);
})
.then(function () {
return TemplateManager.cleanTemporaryTemplateFolder(cordovaParameters.copyFrom);
})
.then(function () {
return TemplateManager.performTokenReplacements(cordovaParameters.projectPath, cordovaParameters.appId, cordovaParameters.appName);
})
.then(function () {
return Q.resolve(self.templateName);
});
};
/**
* Get a list of the available templates for a kit
*
* @param {string} The id of the desired kit
*
* @return {ITemplateList} An object containing the kitId and its available templates
*/
TemplateManager.prototype.getTemplatesForKit = function (kitId) {
return this.kitHelper.getTemplatesForKit(kitId)
.then(function (kitOverride) {
return Q.resolve({
kitId: kitOverride.kitId,
templates: kitOverride.templates.map(function (t) { return new TemplateDescriptor(t); })
});
});
};
/**
* Get a list of all available templates
*
* @return {ITemplateList} An object containing the kitId and its available templates
*/
TemplateManager.prototype.getAllTemplates = function () {
return this.kitHelper.getAllTemplates()
.then(function (results) {
return Q.resolve({
kitId: "",
templates: results.map(function (templateInfo) { return new TemplateDescriptor(templateInfo); })
});
});
};
/**
* Get the number of entries in the specified template
*
* @param {string} kitId The id of the desired kit
* @param {string} templateId The id of the desired template
*
* @return {Q.Promise<number>} A promise resolved with the number of entries in the template
*/
TemplateManager.prototype.getTemplateEntriesCount = function (kitId, templateId) {
return this.kitHelper.getTemplateOverrideInfo(kitId, templateId)
.then(function (templateOverrideInfo) {
var templateZip = new AdmZip(templateOverrideInfo.templateInfo.url);
return templateZip.getEntries().length;
});
};
TemplateManager.prototype.acquireTemplate = function (templateId, kitId) {
if (/^https?:\/\//.test(templateId)) {
this.templateName = resources.getString("CommandCreateGitTemplateName");
return TemplateManager.acquireFromGit(templateId)
.then(function (templateLocation) {
return templateLocation;
});
}
else {
return this.acquireFromTacoKits(templateId, kitId);
}
};
TemplateManager.prototype.acquireFromTacoKits = function (templateId, kitId) {
var self = this;
return this.kitHelper.getTemplateOverrideInfo(kitId, templateId)
.then(function (templateOverrideForKit) {
var templateInfo = templateOverrideForKit.templateInfo;
var extractDestination = TemplateManager.createUnusedFolderPath();
if (!fs.existsSync(templateInfo.url)) {
return Q.reject(errorHelper.get(TacoErrorCodes.CommandCreateTemplatesUnavailable));
}
self.templateName = templateInfo.name;
wrench.mkdirSyncRecursive(extractDestination, 511); // 511 decimal is 0777 octal
// Extract the template archive to the temporary folder
var templateZip = new AdmZip(templateInfo.url);
templateZip.extractAllTo(extractDestination);
// Return the extract destination as the template source path
return Q.resolve(extractDestination);
});
};
TemplateManager.DEFAULT_TEMPLATE_ID = "blank";
TemplateManager.IGNORE_FILES_LIST = [
".gitignore",
".gitattributes",
".taco-ignore"
];
TemplateManager.IGNORE_DIRS_LIST = [
".git"
];
TemplateManager.TEMPORARY_TEMPLATE_PREFIX = "taco_template_";
return TemplateManager;
}());
module.exports = TemplateManager;
//# sourceMappingURL=templateManager.js.map