quip-cli
Version:
A Command Line Interface for the Quip Live Apps platform
304 lines (303 loc) • 13.5 kB
JavaScript
;
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(),
}),
};