UNPKG

@google/clasp

Version:

Develop Apps Script Projects locally

260 lines (259 loc) 13.6 kB
"use strict"; 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;