@google/clasp
Version:
Develop Apps Script Projects locally
116 lines (115 loc) • 5.54 kB
JavaScript
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file defines the 'push' command for the clasp CLI.
import path from 'path';
import { Command } from 'commander';
import inquirer from 'inquirer';
import { intl } from '../intl.js';
import { isInteractive, withSpinner } from './utils.js';
export const command = new Command('push')
.description('Update the remote project')
.option('-f, --force', 'Forcibly overwrites the remote manifest.')
.option('-w, --watch', 'Watches for local file changes. Pushes when a non-ignored file changes.')
.action(async function () {
const options = this.optsWithGlobals();
const clasp = options.clasp;
const watch = options.watch;
let force = options.force; // Store the force option, as it can be updated by confirmManifestUpdate
// Defines the action to take when a file change is detected (either initially or during watch mode).
const onChange = async (paths) => {
// Check if the manifest file (appsscript.json) is among the changed files.
const isManifestUpdated = paths.findIndex(p => path.basename(p) === 'appsscript.json') !== -1;
// If the manifest is updated and not using --force, prompt the user for confirmation.
if (isManifestUpdated && !force) {
force = await confirmManifestUpdate(); // Update force based on user's choice.
if (!force) {
// If user declines manifest overwrite, skip the push.
if (!options.json) {
const msg = intl.formatMessage({ id: "TItFfu", defaultMessage: [{ type: 0, value: "Skipping push." }] });
console.log(msg);
}
return; // Exit onChange without pushing.
}
}
const spinnerMsg = intl.formatMessage({ id: "qUq++d", defaultMessage: [{ type: 0, value: "Pushing files..." }] });
// Perform the push operation using the core `clasp.files.push()` method.
const files = await withSpinner(spinnerMsg, async () => {
return clasp.files.push();
});
if (options.json) {
console.log(JSON.stringify(files.map(f => f.localPath), null, 2));
return;
}
// Log the result of the push.
const successMessage = intl.formatMessage({ id: "aD3XSt", defaultMessage: [{ type: 0, value: "Pushed " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
count: files.length,
});
console.log(successMessage);
files.forEach(f => console.log(`└─ ${f.localPath}`));
return true; // Indicate that the push was attempted (or successfully skipped by user).
};
// Initial check for pending changes when the command is first run.
const pendingChanges = await clasp.files.getChangedFiles();
if (pendingChanges.length) {
// If there are changes, map them to their paths and call onChange.
const paths = pendingChanges.map(f => f.localPath);
await onChange(paths);
}
else {
if (options.json) {
console.log(JSON.stringify([], null, 2));
return;
}
// If no changes, inform the user.
const msg = intl.formatMessage({ id: "X/QgBZ", defaultMessage: [{ type: 0, value: "Script is already up to date." }] });
console.log(msg);
}
// If not in watch mode, exit after the initial push attempt.
if (!watch) {
return;
}
// Setup for watch mode.
const onReady = async () => {
const msg = intl.formatMessage({ id: "m/C0lF", defaultMessage: [{ type: 0, value: "Waiting for changes..." }] });
console.log(msg);
};
// Start watching local files. The `onChange` function will be called on subsequent changes.
// `watchLocalFiles` returns a function to stop the watcher.
const stopWatching = await clasp.files.watchLocalFiles(onReady, async (paths) => {
// If onChange returns undefined (e.g. user skipped manifest push), it implies we should stop watching.
// This can happen if the user cancels the manifest push in interactive mode.
if (!(await onChange(paths))) {
stopWatching();
}
});
});
/**
* Confirms that the manifest file has been updated.
* @returns {Promise<boolean>}
*/
async function confirmManifestUpdate() {
if (!isInteractive()) {
return false;
}
const prompt = intl.formatMessage({ id: "Dh7naZ", defaultMessage: [{ type: 0, value: "Manifest file has been updated. Do you want to push and overwrite?" }] });
const answer = await inquirer.prompt([
{
default: false,
message: prompt,
name: 'overwrite',
type: 'confirm',
},
]);
return answer.overwrite;
}