@google/clasp
Version:
Develop Apps Script Projects locally
260 lines (259 loc) • 13.6 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
var anymatch = require("anymatch");
var mkdirp = require("mkdirp");
var recursive = require("recursive-readdir");
var auth_1 = require("./auth");
var utils_1 = require("./utils");
var path = require('path');
var readMultipleFiles = require('read-multiple-files');
/**
* Gets the local file type from the API FileType.
* @param {string} type The file type returned by Apps Script
* @return {string} The file type
* @see https://developers.google.com/apps-script/api/reference/rest/v1/File#FileType
*/
function getFileType(type) {
return (type === 'SERVER_JS') ? 'js' : type.toLowerCase();
}
exports.getFileType = getFileType;
/**
* Returns true if the user has a clasp project.
* @returns {boolean} If .clasp.json exists.
*/
function hasProject() {
return fs.existsSync(utils_1.DOT.PROJECT.PATH);
}
exports.hasProject = hasProject;
/**
* Recursively finds all files that are part of the current project, and those that are ignored
* by .claspignore and calls the passed callback function with the file lists.
* @param {string} rootDir The project's root directory
* @param {FilesCallBack} callback The callback will be called with the following paramters
* error: Error if there's an error, otherwise null
* result: string[][], List of two lists of strings, ie. [nonIgnoredFilePaths,ignoredFilePaths]
* files?: Array<AppsScriptFile|undefined> Array of AppsScriptFile objects used by clasp push
*/
function getProjectFiles(rootDir, callback) {
// Read all filenames as a flattened tree
recursive(rootDir || path.join('.', '/'), function (err, filePaths) {
if (err)
return callback(err, null, null);
// Filter files that aren't allowed.
filePaths = filePaths.filter(function (name) { return !name.startsWith('.'); });
utils_1.DOTFILE.IGNORE().then(function (ignorePatterns) {
filePaths = filePaths.sort(); // Sort files alphanumerically
var abortPush = false;
var nonIgnoredFilePaths = [];
var ignoredFilePaths = [];
// Match the files with ignored glob pattern
readMultipleFiles(filePaths, 'utf8', function (err, contents) {
if (err)
return callback(new Error(err), null, null);
// Check if there are any .gs files
// We will prompt the user to rename files
//
// TODO: implement renaming files from .gs to .js
// let canRenameToJS = false;
// filePaths.map((name, i) => {
// if (path.extname(name) === '.gs') {
// canRenameToJS = true;
// }
// });
// Check if there are files that will conflict if renamed .gs to .js
filePaths.map(function (name) {
var fileNameWithoutExt = name.slice(0, -path.extname(name).length);
if (filePaths.indexOf(fileNameWithoutExt + '.js') !== -1 &&
filePaths.indexOf(fileNameWithoutExt + '.gs') !== -1) {
// Can't rename, conflicting files
abortPush = true;
if (path.extname(name) === '.gs') { // only print error once (for .gs)
utils_1.logError(null, utils_1.ERROR.CONFLICTING_FILE_EXTENSION(fileNameWithoutExt));
}
}
});
if (abortPush)
return callback(new Error(), null, null);
var files = filePaths.map(function (name, i) {
var nameWithoutExt = name.slice(0, -path.extname(name).length);
// Replace OS specific path separator to common '/' char
nameWithoutExt = nameWithoutExt.replace(/\\/g, '/');
// Formats rootDir/appsscript.json to appsscript.json.
// Preserves subdirectory names in rootDir
// (rootDir/foo/Code.js becomes foo/Code.js)
var formattedName = nameWithoutExt;
if (rootDir) {
formattedName = nameWithoutExt.slice(rootDir.length + 1, nameWithoutExt.length);
}
if (utils_1.getAPIFileType(name) && !anymatch(ignorePatterns, name)) {
nonIgnoredFilePaths.push(name);
var file = {
name: formattedName,
type: utils_1.getAPIFileType(name),
source: contents[i],
};
return file;
}
else {
ignoredFilePaths.push(name);
return; // Skip ignored files
}
}).filter(Boolean); // remove null values
callback(false, [nonIgnoredFilePaths, ignoredFilePaths], files);
});
});
});
}
exports.getProjectFiles = getProjectFiles;
/**
* Fetches the files for a project from the server and writes files locally to
* `pwd` with dots converted to subdirectories.
* @param {string} scriptId The project script id
* @param {string?} rootDir The directory to save the project files to. Defaults to `pwd`
* @param {number?} versionNumber The version of files to fetch.
*/
function fetchProject(scriptId, rootDir, versionNumber) {
if (rootDir === void 0) { rootDir = ''; }
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, utils_1.checkIfOnline()];
case 1:
_a.sent();
return [4 /*yield*/, auth_1.loadAPICredentials()];
case 2:
_a.sent();
utils_1.spinner.start();
auth_1.script.projects.getContent({
scriptId: scriptId,
versionNumber: versionNumber,
}, {}, function (error, res) {
utils_1.spinner.stop(true);
if (error) {
if (error.statusCode === 404)
return utils_1.logError(null, utils_1.ERROR.SCRIPT_ID_INCORRECT(scriptId));
return utils_1.logError(error, utils_1.ERROR.SCRIPT_ID);
}
else {
var data = res.data;
if (!data.files) {
return utils_1.logError(null, utils_1.ERROR.SCRIPT_ID_INCORRECT(scriptId));
}
// Create the files in the cwd
console.log(utils_1.LOG.CLONE_SUCCESS(data.files.length));
var sortedFiles = data.files.sort(function (file) { return file.name; });
sortedFiles.map(function (file) {
var filePath = file.name + "." + getFileType(file.type);
var truePath = (rootDir || '.') + "/" + filePath;
mkdirp(path.dirname(truePath), function (err) {
if (err)
return utils_1.logError(err, utils_1.ERROR.FS_DIR_WRITE);
if (!file.source)
return; // disallow empty files
fs.writeFile(truePath, file.source, function (err) {
if (err)
return utils_1.logError(err, utils_1.ERROR.FS_FILE_WRITE);
});
// Log only filename if pulling to root (Code.gs vs ./Code.gs)
console.log("\u2514\u2500 " + (rootDir ? truePath : filePath));
});
});
}
});
return [2 /*return*/];
}
});
});
}
exports.fetchProject = fetchProject;
/**
* Pushes project files to script.google.com.
*/
function pushFiles() {
return __awaiter(this, void 0, void 0, function () {
var _a, scriptId, rootDir;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, utils_1.getProjectSettings()];
case 1:
_a = _b.sent(), scriptId = _a.scriptId, rootDir = _a.rootDir;
if (!scriptId)
return [2 /*return*/];
getProjectFiles(rootDir, function (err, projectFiles, files) {
if (err) {
utils_1.logError(err, utils_1.LOG.PUSH_FAILURE);
utils_1.spinner.stop(true);
}
else if (projectFiles) {
var nonIgnoredFilePaths_1 = projectFiles[0];
auth_1.script.projects.updateContent({
scriptId: scriptId,
resource: { files: files },
}, {}, function (error) {
utils_1.spinner.stop(true);
// In the following code, we favor console.error()
// over logError() because logError() exits, whereas
// we want to log multiple lines of messages, and
// eventually exit after logging everything.
if (error) {
console.error(utils_1.LOG.PUSH_FAILURE);
error.errors.map(function (err) {
console.error(err.message);
});
console.error(utils_1.LOG.FILES_TO_PUSH);
nonIgnoredFilePaths_1.map(function (filePath) {
console.error("\u2514\u2500 " + filePath);
});
process.exit(1);
}
else {
nonIgnoredFilePaths_1.map(function (filePath) {
console.log("\u2514\u2500 " + filePath);
});
console.log(utils_1.LOG.PUSH_SUCCESS(nonIgnoredFilePaths_1.length));
}
});
}
});
return [2 /*return*/];
}
});
});
}
exports.pushFiles = pushFiles;
;