@google/clasp
Version:
Develop Apps Script Projects locally
124 lines (123 loc) • 6.99 kB
JavaScript
// 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 defines the 'create-script' (alias 'create') command for the clasp
// CLI.
import path from 'node:path';
import { Command } from 'commander';
import inflection from 'inflection';
import { intl } from '../intl.js';
import { withSpinner } from './utils.js';
// https://developers.google.com/drive/api/v3/mime-types
const DRIVE_FILE_MIMETYPES = {
docs: 'application/vnd.google-apps.document',
forms: 'application/vnd.google-apps.form',
sheets: 'application/vnd.google-apps.spreadsheet',
slides: 'application/vnd.google-apps.presentation',
};
export const command = new Command('create-script')
.alias('create')
.description('Create a script')
.option('--type <type>', 'Creates a new Apps Script project attached to a new Document, Spreadsheet, Presentation, Form, or as a standalone script, web app, or API.', 'standalone')
.option('--title <title>', 'The project title.')
.option('--parentId <id>', 'A project parent Id.')
.option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
.action(async function () {
var _a;
const options = this.optsWithGlobals();
const clasp = options.clasp;
if (clasp.project.exists()) {
const msg = intl.formatMessage({ id: "kk5+4G", defaultMessage: [{ type: 0, value: "Project file already exists." }] });
this.error(msg);
}
// Create defaults.
const parentId = options.parentId;
const name = options.title ? options.title : getDefaultProjectName(process.cwd());
const type = options.type ? options.type.toLowerCase() : 'standalone';
const rootDir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : '.';
clasp.withContentDir(rootDir);
let scriptId;
let createdParentId;
// Handle container-bound script creation (e.g., for Sheets, Docs, Forms, Slides).
if (type && type !== 'standalone') {
const mimeType = DRIVE_FILE_MIMETYPES[type]; // Look up MIME type for the specified container type.
if (!mimeType) {
// If the type is invalid or not supported for container-bound scripts.
const msg = intl.formatMessage({ id: "d2MBtN", defaultMessage: [{ type: 0, value: "Invalid container file type" }] });
this.error(msg);
}
const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
// This call creates both the Google Drive file (e.g., a new Spreadsheet)
// and the Apps Script project bound to it.
const result = await withSpinner(spinnerMsg, async () => await clasp.project.createWithContainer(name, mimeType));
scriptId = result.scriptId;
createdParentId = result.parentId;
if (!options.json) {
const parentUrl = `https://drive.google.com/open?id=${createdParentId}`; // URL to the container file.
const scriptUrl = `https://script.google.com/d/${scriptId}/edit`; // URL to the new Apps Script project.
const successMessage = intl.formatMessage({ id: "yf9wXJ", defaultMessage: [{ type: 0, value: "Created new document: " }, { type: 1, value: "parentUrl" }, { type: 1, value: "br" }, { type: 0, value: "Created new script: " }, { type: 1, value: "scriptUrl" }] }, {
parentUrl,
scriptUrl,
br: '\n',
});
console.log(successMessage);
}
}
else {
// Handle standalone script creation.
const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
// This call creates a standalone Apps Script project.
// If `parentId` is provided, it attempts to create it within that Drive folder.
scriptId = await withSpinner(spinnerMsg, async () => await clasp.project.createScript(name, parentId));
if (!options.json) {
const parentUrl = `https://drive.google.com/open?id=${parentId}`; // URL to parent folder if specified.
const scriptUrl = `https://script.google.com/d/${scriptId}/edit`; // URL to the new Apps Script project.
const successMessage = intl.formatMessage({ id: "0a429S", defaultMessage: [{ type: 0, value: "Created new script: " }, { type: 1, value: "scriptUrl" }, { type: 5, value: "parentId", options: { undefined: { value: [] }, other: { value: [{ type: 1, value: "br" }, { type: 0, value: "Bound to document: " }, { type: 1, value: "parentUrl" }] } } }] }, {
parentId,
parentUrl,
scriptUrl,
br: '\n',
});
console.log(successMessage);
}
}
const spinnerMsg = intl.formatMessage({ id: "UTMHnH", defaultMessage: [{ type: 0, value: "Cloning script..." }] });
// After creating the script (either standalone or container-bound),
// pull its initial files (e.g., Code.gs, appsscript.json) to the local directory.
const files = await withSpinner(spinnerMsg, async () => {
const files = await clasp.files.pull();
// Update the local .clasp.json with the new scriptId and other settings.
clasp.project.updateSettings();
return files;
});
if (options.json) {
console.log(JSON.stringify({ scriptId, parentId: createdParentId, files: files.map(f => f.localPath) }, null, 2));
return;
}
// Log the paths of the pulled files.
files.forEach(f => console.log(`└─ ${f.localPath}`));
const successMessage = intl.formatMessage({ id: "XABSyD", defaultMessage: [{ type: 0, value: "Cloned " }, { 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);
});
/**
* Generates a default project name based on the current directory's basename.
* It humanizes the directory name (e.g., 'my-project-folder' becomes 'My project folder').
* @param {string} dir - The directory path from which to derive the project name.
* @returns {string} The humanized default project name.
*/
export function getDefaultProjectName(dir) {
const dirName = path.basename(dir);
return inflection.humanize(dirName);
}