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
			
			
This commit is contained in:
		@@ -43,6 +43,27 @@ const CommonArgv = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = {
 | 
					const SyncArgv = {
 | 
				
			||||||
  ...CommonArgv,
 | 
					  ...CommonArgv,
 | 
				
			||||||
  commit: {
 | 
					  commit: {
 | 
				
			||||||
@@ -147,24 +168,73 @@ yargs(hideBin(process.argv))
 | 
				
			|||||||
  .scriptName("quartz")
 | 
					  .scriptName("quartz")
 | 
				
			||||||
  .version(version)
 | 
					  .version(version)
 | 
				
			||||||
  .usage("$0 <cmd> [args]")
 | 
					  .usage("$0 <cmd> [args]")
 | 
				
			||||||
  .command("create", "Initialize Quartz", CommonArgv, async (argv) => {
 | 
					  .command("create", "Initialize Quartz", CreateArgv, async (argv) => {
 | 
				
			||||||
    console.log()
 | 
					    console.log()
 | 
				
			||||||
    intro(chalk.bgGreen.black(` Quartz v${version} `))
 | 
					    intro(chalk.bgGreen.black(` Quartz v${version} `))
 | 
				
			||||||
    const contentFolder = path.join(cwd, argv.directory)
 | 
					    const contentFolder = path.join(cwd, argv.directory)
 | 
				
			||||||
    const setupStrategy = exitIfCancel(
 | 
					    let setupStrategy = argv.strategy?.toLowerCase()
 | 
				
			||||||
      await select({
 | 
					    let linkResolutionStrategy = argv.links?.toLowerCase()
 | 
				
			||||||
        message: `Choose how to initialize the content in \`${contentFolder}\``,
 | 
					    const sourceDirectory = argv.source
 | 
				
			||||||
        options: [
 | 
					
 | 
				
			||||||
          { value: "new", label: "Empty Quartz" },
 | 
					    // If all cmd arguments were provided, check if theyre valid
 | 
				
			||||||
          { value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" },
 | 
					    if (setupStrategy && linkResolutionStrategy) {
 | 
				
			||||||
          {
 | 
					      // If setup isn't, "new", source argument is required
 | 
				
			||||||
            value: "symlink",
 | 
					      if (setupStrategy !== "new") {
 | 
				
			||||||
            label: "Symlink an existing folder",
 | 
					        // Error handling
 | 
				
			||||||
            hint: "don't select this unless you know what you are doing!",
 | 
					        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: [
 | 
				
			||||||
 | 
					            { value: "new", label: "Empty Quartz" },
 | 
				
			||||||
 | 
					            { value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              value: "symlink",
 | 
				
			||||||
 | 
					              label: "Symlink an existing folder",
 | 
				
			||||||
 | 
					              hint: "don't select this unless you know what you are doing!",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function rmContentFolder() {
 | 
					    async function rmContentFolder() {
 | 
				
			||||||
      const contentStat = await fs.promises.lstat(contentFolder)
 | 
					      const contentStat = await fs.promises.lstat(contentFolder)
 | 
				
			||||||
@@ -177,23 +247,28 @@ yargs(hideBin(process.argv))
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
 | 
					    await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
 | 
				
			||||||
    if (setupStrategy === "copy" || setupStrategy === "symlink") {
 | 
					    if (setupStrategy === "copy" || setupStrategy === "symlink") {
 | 
				
			||||||
      const originalFolder = escapePath(
 | 
					      let originalFolder = sourceDirectory
 | 
				
			||||||
        exitIfCancel(
 | 
					
 | 
				
			||||||
          await text({
 | 
					      // If input directory was not passed, use cli
 | 
				
			||||||
            message: "Enter the full path to existing content folder",
 | 
					      if (!sourceDirectory) {
 | 
				
			||||||
            placeholder:
 | 
					        originalFolder = escapePath(
 | 
				
			||||||
              "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
 | 
					          exitIfCancel(
 | 
				
			||||||
            validate(fp) {
 | 
					            await text({
 | 
				
			||||||
              const fullPath = escapePath(fp)
 | 
					              message: "Enter the full path to existing content folder",
 | 
				
			||||||
              if (!fs.existsSync(fullPath)) {
 | 
					              placeholder:
 | 
				
			||||||
                return "The given path doesn't exist"
 | 
					                "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
 | 
				
			||||||
              } else if (!fs.lstatSync(fullPath).isDirectory()) {
 | 
					              validate(fp) {
 | 
				
			||||||
                return "The given path is not a folder"
 | 
					                const fullPath = escapePath(fp)
 | 
				
			||||||
              }
 | 
					                if (!fs.existsSync(fullPath)) {
 | 
				
			||||||
            },
 | 
					                  return "The given path doesn't exist"
 | 
				
			||||||
          }),
 | 
					                } else if (!fs.lstatSync(fullPath).isDirectory()) {
 | 
				
			||||||
        ),
 | 
					                  return "The given path is not a folder"
 | 
				
			||||||
      )
 | 
					                }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await rmContentFolder()
 | 
					      await rmContentFolder()
 | 
				
			||||||
      if (setupStrategy === "copy") {
 | 
					      if (setupStrategy === "copy") {
 | 
				
			||||||
@@ -217,29 +292,32 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // get a preferred link resolution strategy
 | 
					    // Use cli process if cmd args werent provided
 | 
				
			||||||
    const linkResolutionStrategy = exitIfCancel(
 | 
					    if (!linkResolutionStrategy) {
 | 
				
			||||||
      await select({
 | 
					      // get a preferred link resolution strategy
 | 
				
			||||||
        message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
 | 
					      linkResolutionStrategy = exitIfCancel(
 | 
				
			||||||
        options: [
 | 
					        await select({
 | 
				
			||||||
          {
 | 
					          message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
 | 
				
			||||||
            value: "absolute",
 | 
					          options: [
 | 
				
			||||||
            label: "Treat links as absolute path",
 | 
					            {
 | 
				
			||||||
            hint: "for content made for Quartz 3 and Hugo",
 | 
					              value: "absolute",
 | 
				
			||||||
          },
 | 
					              label: "Treat links as absolute path",
 | 
				
			||||||
          {
 | 
					              hint: "for content made for Quartz 3 and Hugo",
 | 
				
			||||||
            value: "shortest",
 | 
					            },
 | 
				
			||||||
            label: "Treat links as shortest path",
 | 
					            {
 | 
				
			||||||
            hint: "for most Obsidian vaults",
 | 
					              value: "shortest",
 | 
				
			||||||
          },
 | 
					              label: "Treat links as shortest path",
 | 
				
			||||||
          {
 | 
					              hint: "for most Obsidian vaults",
 | 
				
			||||||
            value: "relative",
 | 
					            },
 | 
				
			||||||
            label: "Treat links as relative paths",
 | 
					            {
 | 
				
			||||||
            hint: "for just normal Markdown files",
 | 
					              value: "relative",
 | 
				
			||||||
          },
 | 
					              label: "Treat links as relative paths",
 | 
				
			||||||
        ],
 | 
					              hint: "for just normal Markdown files",
 | 
				
			||||||
      }),
 | 
					            },
 | 
				
			||||||
    )
 | 
					          ],
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // now, do config changes
 | 
					    // now, do config changes
 | 
				
			||||||
    const configFilePath = path.join(cwd, "quartz.config.ts")
 | 
					    const configFilePath = path.join(cwd, "quartz.config.ts")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user