UNPKG

@google/clasp

Version:

Develop Apps Script Projects locally

152 lines (151 loc) 6.67 kB
// Copyright 2025 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 contains utility functions for the clasp CLI commands, such as // spinners, URL opening, and configuration assertions. import cliTruncate from 'cli-truncate'; import inquirer from 'inquirer'; import open from 'open'; import ora from 'ora'; import { intl } from '../intl.js'; /** * Asserts that a script project is configured by checking for a script ID. * Throws an error if the script ID is not set. * @param {Clasp} clasp - The Clasp instance containing project details. * @throws {Error} If `clasp.project.scriptId` is not set. */ export function assertScriptConfigured(clasp) { if (clasp.project.scriptId) { return; } const msg = intl.formatMessage({ id: "2IuvqO", defaultMessage: [{ type: 0, value: "Script ID is not set, unable to continue." }] }); throw new Error(msg); } /** * Asserts that a Google Cloud Platform (GCP) project is configured by checking for a project ID. * Throws an error if the GCP project ID is not set. * @param {Clasp} clasp - The Clasp instance containing project details. * @throws {Error} If `clasp.project.projectId` is not set. */ export function assertGcpProjectConfigured(clasp) { if (clasp.project.projectId) { return; } const msg = intl.formatMessage({ id: "aD3+By", defaultMessage: [{ type: 0, value: "GCP project ID is not set, unable to continue." }] }); throw new Error(msg); } /** * Prompts the user for a Google Cloud Platform (GCP) project ID if one is not already configured * and the environment is interactive. It opens the script's GCP settings page in the browser * to help the user find their project ID. * @param {Clasp} clasp - The Clasp instance. * @returns {Promise<string | undefined>} The GCP project ID if available or entered by the user, otherwise undefined. */ export async function maybePromptForProjectId(clasp) { let projectId = clasp.project.getProjectId(); if (!projectId && isInteractive()) { assertScriptConfigured(clasp); const url = `https://script.google.com/home/projects/${clasp.project.scriptId}/settings`; const instructions = intl.formatMessage({ id: "9gtRgW", defaultMessage: [{ type: 0, value: "The script is not bound to a GCP project. To view or configure the GCP project for this script, open " }, { type: 1, value: "url" }, { type: 0, value: " in your browser and follow instructions for setting up a GCP project. If a project is already configured, open the GCP project to get the project ID value." }] }, { url, }); console.log(instructions); await openUrl(url); const prompt = intl.formatMessage({ id: "Zde2DB", defaultMessage: [{ type: 0, value: "What is your GCP projectId?" }] }); const answer = await inquirer.prompt([ { message: prompt, name: 'projectId', type: 'input', }, ]); projectId = answer.projectId; await clasp.project.setProjectId(projectId); } return projectId; } const spinner = ora(); /** * Executes an asynchronous function while displaying a spinner in the console. * The spinner is only shown if the environment is interactive. * @template T * @param {string} message - The message to display next to the spinner. * @param {() => Promise<T>} fn - The asynchronous function to execute. * @returns {Promise<T>} A promise that resolves with the result of the executed function. */ export async function withSpinner(message, fn) { // If not interactive terminal, skip spinner if (!isInteractive()) { return await fn(); } spinner.start(message); try { return await fn(); } finally { if (spinner.isSpinning) { spinner.stop(); } } } /** * Truncates a string to a specified length with an ellipsis if it exceeds the length. * It attempts to truncate on a space and pads the end to maintain the specified length. * @param {string} value - The string to ellipsize. * @param {number} length - The maximum length of the string. * @returns {string} The ellipsized string. */ export function ellipsize(value, length) { return cliTruncate(value, length, { preferTruncationOnSpace: true }).padEnd(length); } /** * Environment flags for clasp, primarily used for testing purposes * to simulate or disable interactive states or browser presence. * @property {boolean} isInteractive - Whether the current environment is an interactive TTY. * @property {boolean} isBrowserPresent - Whether a browser is considered available to open URLs. */ // Exporting and wrapping to allow it to be toggled in tests export const claspEnv = { isInteractive: process.stdout.isTTY, isBrowserPresent: process.stdout.isTTY, }; /** * Checks if the current environment is interactive (i.e., a TTY). * Relies on `claspEnv.isInteractive`. * @returns {boolean} True if interactive, false otherwise. */ export function isInteractive() { return claspEnv.isInteractive; } /** * Opens a URL in the default web browser if available. * If a browser is not considered present (e.g., in some CI environments, or for testing), * it logs a message asking the user to open the URL manually. * @param {string} url - The URL to open. * @returns {Promise<void | import('open').ChildProcess>} A promise that resolves when the URL is opened, * or void if a browser is not present. The child process from `open` is returned if a browser is used. */ export async function openUrl(url) { if (!claspEnv.isBrowserPresent) { const msg = intl.formatMessage({ id: "kvR0OI", defaultMessage: [{ type: 0, value: "Open " }, { type: 1, value: "url" }, { type: 0, value: " in your browser to continue." }] }, { url, }); console.log(msg); return; } const msg = intl.formatMessage({ id: "IVffJ2", defaultMessage: [{ type: 0, value: "Opening " }, { type: 1, value: "url" }, { type: 0, value: " in your browser." }] }, { url, }); console.log(msg); return await open(url, { wait: false }); }