Ben Schlegel
2023-08-26 ad4145fb10dbf32d8f99e1de555339dba0979f72
feat: support CLI arguments for `npx quartz create` (#421)

* feat(cli): add new args for content + link resolve

* feat(cli): validate cmd args

* feat(cli): add chalk + error code to errors

* feat(cli): support for setup/link via args

* refactor(cli): use yargs choices instead of manual

Scrap manual check if arguments are valid, use yargs "choices" field instead.

* feat(cli): add in-dir argument+ handle errors

add new "in-directory" argument, used if "setup" is "copy" or "symlink" to determine source. add error handling for invalid permutations of arguments or non existent path

* feat(cli): dynamically use cli or provided args

use "in-directory" arg as `originalFolder` if available, otherwise get it from manual cli process

* run format

* fix: use process.exit instead of return

* refactor: split CommonArgv and CreateArgv

* refactor(cli): rename create args, use ${} syntax

* fix(cli): fix link resolution strategy arg

* format

* feat(consistency): allow partial cmd args
1 files modified
86 ■■■■■ changed files
quartz/bootstrap-cli.mjs 86 ●●●●● patch | view | raw | blame | history
quartz/bootstrap-cli.mjs
@@ -43,6 +43,27 @@
  },
}
const CreateArgv = {
  ...CommonArgv,
  source: {
    string: true,
    alias: ["s"],
    describe: "source directory to copy/create symlink from",
  },
  strategy: {
    string: true,
    alias: ["X"],
    choices: ["new", "copy", "symlink"],
    describe: "strategy for content folder setup",
  },
  links: {
    string: true,
    alias: ["l"],
    choices: ["absolute", "shortest", "relative"],
    describe: "strategy to resolve links",
  },
}
const SyncArgv = {
  ...CommonArgv,
  commit: {
@@ -147,11 +168,59 @@
  .scriptName("quartz")
  .version(version)
  .usage("$0 <cmd> [args]")
  .command("create", "Initialize Quartz", CommonArgv, async (argv) => {
  .command("create", "Initialize Quartz", CreateArgv, async (argv) => {
    console.log()
    intro(chalk.bgGreen.black(` Quartz v${version} `))
    const contentFolder = path.join(cwd, argv.directory)
    const setupStrategy = exitIfCancel(
    let setupStrategy = argv.strategy?.toLowerCase()
    let linkResolutionStrategy = argv.links?.toLowerCase()
    const sourceDirectory = argv.source
    // If all cmd arguments were provided, check if theyre valid
    if (setupStrategy && linkResolutionStrategy) {
      // If setup isn't, "new", source argument is required
      if (setupStrategy !== "new") {
        // Error handling
        if (!sourceDirectory) {
          outro(
            chalk.red(
              `Setup strategies (arg '${chalk.yellow(
                `-${CreateArgv.strategy.alias[0]}`,
              )}') other than '${chalk.yellow(
                "new",
              )}' require content folder argument ('${chalk.yellow(
                `-${CreateArgv.source.alias[0]}`,
              )}') to be set`,
            ),
          )
          process.exit(1)
        } else {
          if (!fs.existsSync(sourceDirectory)) {
            outro(
              chalk.red(
                `Input directory to copy/symlink 'content' from not found ('${chalk.yellow(
                  sourceDirectory,
                )}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`,
              ),
            )
            process.exit(1)
          } else if (!fs.lstatSync(sourceDirectory).isDirectory()) {
            outro(
              chalk.red(
                `Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow(
                  sourceDirectory,
                )}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`,
              ),
            )
            process.exit(1)
          }
        }
      }
    }
    // Use cli process if cmd args werent provided
    if (!setupStrategy) {
      setupStrategy = exitIfCancel(
      await select({
        message: `Choose how to initialize the content in \`${contentFolder}\``,
        options: [
@@ -165,6 +234,7 @@
        ],
      }),
    )
    }
    async function rmContentFolder() {
      const contentStat = await fs.promises.lstat(contentFolder)
@@ -177,7 +247,11 @@
    await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
    if (setupStrategy === "copy" || setupStrategy === "symlink") {
      const originalFolder = escapePath(
      let originalFolder = sourceDirectory
      // If input directory was not passed, use cli
      if (!sourceDirectory) {
        originalFolder = escapePath(
        exitIfCancel(
          await text({
            message: "Enter the full path to existing content folder",
@@ -194,6 +268,7 @@
          }),
        ),
      )
      }
      await rmContentFolder()
      if (setupStrategy === "copy") {
@@ -217,8 +292,10 @@
      )
    }
    // Use cli process if cmd args werent provided
    if (!linkResolutionStrategy) {
    // get a preferred link resolution strategy
    const linkResolutionStrategy = exitIfCancel(
      linkResolutionStrategy = exitIfCancel(
      await select({
        message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
        options: [
@@ -240,6 +317,7 @@
        ],
      }),
    )
    }
    // now, do config changes
    const configFilePath = path.join(cwd, "quartz.config.ts")