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:
Ben Schlegel 2023-08-26 22:21:44 +02:00 committed by GitHub
parent 74c3ebb7bd
commit ad4145fb10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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 = {
...CommonArgv,
commit: {
@ -147,24 +168,73 @@ yargs(hideBin(process.argv))
.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(
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!",
},
],
}),
)
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: [
{ 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() {
const contentStat = await fs.promises.lstat(contentFolder)
@ -177,23 +247,28 @@ yargs(hideBin(process.argv))
await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
if (setupStrategy === "copy" || setupStrategy === "symlink") {
const originalFolder = escapePath(
exitIfCancel(
await text({
message: "Enter the full path to existing content folder",
placeholder:
"On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
validate(fp) {
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"
}
},
}),
),
)
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",
placeholder:
"On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
validate(fp) {
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()
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
const linkResolutionStrategy = exitIfCancel(
await select({
message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
options: [
{
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: "relative",
label: "Treat links as relative paths",
hint: "for just normal Markdown files",
},
],
}),
)
// Use cli process if cmd args werent provided
if (!linkResolutionStrategy) {
// get a preferred link resolution strategy
linkResolutionStrategy = exitIfCancel(
await select({
message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
options: [
{
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: "relative",
label: "Treat links as relative paths",
hint: "for just normal Markdown files",
},
],
}),
)
}
// now, do config changes
const configFilePath = path.join(cwd, "quartz.config.ts")