UNPKG

quip-cli

Version:

A Command Line Interface for the Quip Live Apps platform

304 lines (303 loc) 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const command_1 = require("@oclif/command"); const chalk_1 = tslib_1.__importDefault(require("chalk")); const fs_1 = tslib_1.__importDefault(require("fs")); const inquirer_1 = tslib_1.__importDefault(require("inquirer")); const path_1 = tslib_1.__importDefault(require("path")); const cli_api_1 = tslib_1.__importStar(require("../lib/cli-api")); const config_1 = require("../lib/config"); const print_1 = require("../lib/print"); const util_1 = require("../lib/util"); const bump_1 = require("./bump"); const publish_1 = require("./publish"); const release_1 = require("./release"); const defaultName = (dir) => { return path_1.default .basename(path_1.default.resolve(process.cwd(), dir || "")) .replace(/[^\w\d\s]/g, " ") .replace(/(:?^|\s)(\w)/g, (c) => c.toUpperCase()); }; const packageName = (name) => name.toLowerCase().trim().replace(/\s+/g, "-"); const getAppDir = (name, dir) => { if (dir && dir.length) { if (path_1.default.isAbsolute(dir)) { return dir; } return path_1.default.join(process.cwd(), dir); } return path_1.default.join(process.cwd(), name); }; const defaultPackageOptions = (name) => ({ name: packageName(name), description: "A Quip Live App", typescript: true, }); const defaultManifestOptions = (dir, opts) => (Object.assign({ name: defaultName(dir) }, (opts || {}))); class Init extends command_1.Command { constructor() { super(...arguments); this.promptInitialAppConfig_ = async (specifiedDir, defaults) => { print_1.println("Creating a new Quip Live App"); const validateNumber = (val) => !isNaN(parseInt(val, 10)) || "Please enter a number"; const defaultManifest = defaultManifestOptions(specifiedDir, defaults); const manifestOptions = await inquirer_1.default.prompt([ { type: "input", name: "name", message: "What is the name of this app?\n(This is what users will see when inserting your app)\n", default: defaultManifest.name, }, { type: "list", name: "toolbar_color", message: "Choose a toolbar color", choices: ["red", "orange", "yellow", "green", "blue", "violet"], }, ]); const defaultPackage = defaultPackageOptions(manifestOptions.name); const packageOptions = await inquirer_1.default.prompt([ { type: "input", name: "name", message: "Choose a package name", default: defaultPackage.name, filter: (val) => val.toLowerCase(), }, { type: "input", name: "description", message: "What does this app do?\n", default: defaultPackage.description, }, { type: "confirm", name: "typescript", message: "Use Typescript?", default: defaultPackage.typescript, }, ]); const { addManifestConfig } = await inquirer_1.default.prompt([ { type: "confirm", name: "addManifestConfig", message: "Would you like to customize your manifest.json now?\n(see: https://corp.quip.com/dev/liveapps/documentation#app-manifest)\n", default: true, }, ]); if (addManifestConfig) { const extraManifestOptions = await inquirer_1.default.prompt([ { type: "confirm", name: "disable_app_level_comments", message: "Disable commenting at the app level?", default: false, }, { type: "list", name: "sizing_mode", message: "Choose a sizing mode\n(see: https://corp.quip.com/dev/liveapps/recipes#specifying-the-size-of-your-app)", choices: ["fill_container", "fit_content", "scale"], }, { type: "number", name: "initial_height", message: "Specify an initial height for your app\nThis will be the height of the app while it is loading.\n", default: 300, validate: validateNumber, filter: Number, }, { type: "number", name: "initial_width", message: "Specify an initial width for your app (optional)\nThis will be the width of the app while it is loading.\n", default: "none", validate: (input) => input === "none" || validateNumber(input), filter: (val) => (val === "none" ? -1 : val), }, ]); Object.assign(manifestOptions, extraManifestOptions); } packageOptions.bundler = "webpack"; manifestOptions.description = packageOptions.description; const appDir = getAppDir(packageOptions.name, specifiedDir); return { appDir, packageOptions, manifestOptions, }; }; this.copyTemplate_ = (packageOptions, dest, dryRun) => { const { typescript, bundler } = packageOptions; // get lib path const templateName = `${typescript ? "ts" : "js"}_${bundler || "webpack"}`; const templatePath = path_1.default.join(__dirname, "../../templates", templateName); const options = { dereference: true, // For use during local developement. Do not copy .git and boilerplate node_modules filter: (fileName) => !fileName.match(/(?:\.git\/|templates\/[\w_]+\/node_modules)/), }; if (dryRun) { print_1.println(`Would intialize ${templateName} on ${dest}`); return; } else { return util_1.copy(templatePath, dest, options); } }; this.mutatePackage_ = (packageOptions, dir) => { const packagePath = path_1.default.join(dir, "package.json"); return this.mutateJsonConfig_(packagePath, packageOptions); }; this.mutateManifest_ = (manifestOptions, dir) => { const manifestPath = path_1.default.join(dir, "manifest.json"); return this.mutateJsonConfig_(manifestPath, manifestOptions); }; this.mutateJsonConfig_ = async (configPath, updates) => { const config = JSON.parse(fs_1.default.readFileSync(configPath).toString()); Object.assign(config, updates); await fs_1.default.promises.writeFile(configPath, JSON.stringify(config, null, 4)); return config; }; } async run() { const { flags } = this.parse(Init); const dryRun = flags["dry-run"]; const fetch = await cli_api_1.default(flags.config, flags.site); const shouldCreate = !flags["no-create"]; if (shouldCreate) { const ok = await cli_api_1.successOnly(fetch("ok"), false); if (!ok) { print_1.println(chalk_1.default `{red Refusing to create an app since we can't contact Quip servers.}`); process.exit(1); } } let config; if (flags.name && flags.json && flags.id) { const manifestOptions = defaultManifestOptions(flags.dir, { name: flags.name, id: flags.id, }); const packageOptions = defaultPackageOptions(manifestOptions.name); config = { appDir: getAppDir(packageOptions.name, flags.dir), manifestOptions, packageOptions, }; } else { // initial app options from user config = await this.promptInitialAppConfig_(flags.dir, { name: flags.name, id: flags.id, }); } const { packageOptions, manifestOptions, appDir } = config; await this.copyTemplate_(packageOptions, appDir, dryRun); if (dryRun) { print_1.println("Would update package.json with", packageOptions); print_1.println("Would update manifest.json with", manifestOptions); return; } let pkg = await this.mutatePackage_(packageOptions, appDir); let manifest = await this.mutateManifest_(manifestOptions, appDir); let success = true; if (!flags["no-create"]) { // create app print_1.println(chalk_1.default `{green Creating app in dev console...}`); const createdApp = await cli_api_1.successOnly(fetch("apps", "post"), false); if (!createdApp) { return; } // update manifest manifest = await this.mutateManifest_({ id: createdApp.id, version_number: createdApp.version_number, version_name: createdApp.version_name, }, appDir); print_1.println(chalk_1.default `{magenta App created: ${createdApp.id}, v${createdApp.version_name} (${createdApp.version_number})}`); // npm install print_1.println(chalk_1.default `{green installing dependencies...}`); await util_1.runCmd(appDir, config_1.NPM_BINARY_NAME, "install"); // bump the version since we already have a version 0 in the console print_1.println(chalk_1.default `{green bumping version...}`); await bump_1.bump(appDir, "minor", { silent: flags.json }); // npm run build print_1.println(chalk_1.default `{green building app...}`); await util_1.runCmd(appDir, config_1.NPM_BINARY_NAME, "run", "build"); // then publish the new version print_1.println(chalk_1.default `{green uploading bundle...}`); const newManifest = await publish_1.doPublish(manifest, path_1.default.join(appDir, "manifest.json"), "node_modules", flags.config, flags.site, flags.json); success = !!newManifest; if (success && !flags["no-release"]) { print_1.println(chalk_1.default `{green releasing build ${newManifest.version_number} as initial beta...}`); await release_1.release({ manifest: newManifest, destination: release_1.ReleaseDestination.BETA, build: newManifest.version_number, site: flags.site, config: flags.config, json: flags.json, }); } if (flags.json) { if (success) { print_1.print(JSON.stringify(newManifest)); } else { process.exit(1); } } } else if (flags.json && success) { print_1.print(JSON.stringify(manifest)); } if (!flags.json && success) { print_1.println(chalk_1.default `{magenta Live App Project initialized: ${manifest.name} (${pkg.name})}`); } } } exports.default = Init; Init.description = "Initialize a new Live App Project"; Init.flags = { help: command_1.flags.help({ char: "h" }), ["dry-run"]: command_1.flags.boolean({ char: "d", hidden: true, description: "Print what this would do, but don't create any files.", }), "no-create": command_1.flags.boolean({ description: "only create a local app (don't create an app in the dev console or assign an ID)", }), "no-release": command_1.flags.boolean({ description: 'don\'t release the initial version (leave app uninstallable and in the "unreleased" state)', }), dir: command_1.flags.string({ char: "d", description: "specify directory to create app in (defaults to the name provided)", }), site: command_1.flags.string({ char: "s", description: "use a specific quip site rather than the standard quip.com login", default: config_1.DEFAULT_SITE, }), json: command_1.flags.boolean({ char: "j", description: "output responses in JSON (must provide --name and --id)", dependsOn: ["name", "id"], }), name: command_1.flags.string({ char: "n", description: "set the name of the application", }), id: command_1.flags.string({ char: "i", description: "set the ID of the application", }), config: command_1.flags.string({ hidden: true, description: "Use a custom config file (default ~/.quiprc)", default: () => config_1.defaultConfigPath(), }), };