feat: list command

This commit is contained in:
2024-02-21 02:29:21 +02:00
committed by Chen Asraf
parent 3765398ab9
commit d579c09c11
5 changed files with 129 additions and 63 deletions

View File

@@ -11,24 +11,24 @@ Usage: simple-scaffold [options]
To see this and more information anytime, add the `-h` or `--help` flag to your call, e.g.
`npx simple-scaffold@latest -h`.
| Command \| alias | |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--name` \| `-n` | Name to be passed to the generated files. `{{name}}` and other data parameters inside contents and file names will be replaced accordingly. You may omit the `--name` or `-n` for this specific option. |
| `--config`\|`-c` | Filename to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax. This can also work in conjunction with `--git` or `--github` to point to remote files, and with `--key` to denote which key to select from the file., |
| `--git`\|`-g` | Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax. |
| `--key` \| `-k` | Key to load inside the config file. This overwrites the config key provided after the colon in `--config` (e.g. `--config scaffold.cmd.js:component`) |
| `--output` \| `-o` | Path to output to. If `--create-sub-folder` is enabled, the subfolder will be created inside this path. Default is current working directory. |
| `--templates` \| `-t` | Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, or a glob pattern for multiple file matching easily. |
| `--overwrite` \| `-w` | Enable to override output files, even if they already exist. |
| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
| `--append-data` \| `-D` | Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, which is easier to use with CLI: `-D key1=string -D key2:=raw` |
| `--create-sub-folder` \| `-s` | Create subfolder with the input name |
| `--sub-folder-name-helper` \| `-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`) |
| `--log-level` \| `-l` | Determine amount of logs to display. The values are: `none \| debug \| info \| warn \| error`. The provided level will display messages of the same level or higher. |
| `--dry-run` \| `-dr` | Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. |
| `--help` \| `-h` | Show this help message |
| `--version` \| `-v` | Display version. |
| Command \| alias | |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--name` \| `-n` | Name to be passed to the generated files. `{{name}}` and other data parameters inside contents and file names will be replaced accordingly. You may omit the `--name` or `-n` for this specific option. |
| `--config`\|`-c` | Filename or directory to load config from |
| `--git`\|`-g` | Git URL or GitHub path to load a template from. |
| `--key` \| `-k` | Key to load inside the config file. This overwrites the config key provided after the colon in `--config` (e.g. `--config scaffold.cmd.js:component`) |
| `--output` \| `-o` | Path to output to. If `--create-sub-folder` is enabled, the subfolder will be created inside this path. Default is current working directory. |
| `--templates` \| `-t` | Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, or a glob pattern for multiple file matching easily. |
| `--overwrite` \| `-w` | Enable to override output files, even if they already exist. |
| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
| `--append-data` \| `-D` | Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, which is easier to use with CLI: `-D key1=string -D key2:=raw` |
| `--create-sub-folder` \| `-s` | Create subfolder with the input name |
| `--sub-folder-name-helper` \| `-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`) |
| `--log-level` \| `-l` | Determine amount of logs to display. The values are: `none \| debug \| info \| warn \| error`. The provided level will display messages of the same level or higher. |
| `--dry-run` \| `-dr` | Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. |
| `--help` \| `-h` | Show this help message |
| `--version` \| `-v` | Display version. |
## Examples:

View File

@@ -13,20 +13,14 @@ module.exports = {
"@semantic-release/npm",
{
// only update the pkg version on root, don't publish
// this is to keep package.json version in sync with the release
npmPublish: false,
},
],
// [
// '@semantic-release/npm',
// {
// // only update the pkg version on doc, don't publish
// npmPublish: false,
// pkgRoot: 'doc',
// },
// ]
[
"@semantic-release/exec",
{
// pack the dist folder, during publish step (after version was bumped)
publishCmd: 'echo "Packing..."; cd ./dist && pnpm pack --pack-destination=../; echo "Done"',
},
],
@@ -34,10 +28,12 @@ module.exports = {
"@semantic-release/npm",
{
// publish from dist dir instead of root
// this is the actual uild output
pkgRoot: "dist",
},
],
[
// Release to GitHub
"@semantic-release/github",
{
assets: ["*.tgz"],
@@ -45,6 +41,7 @@ module.exports = {
],
branch === "master"
? [
// Update CHANGELOG.md only on master
"@semantic-release/changelog",
{
changelogFile: "CHANGELOG.md",
@@ -53,6 +50,7 @@ module.exports = {
]
: undefined,
[
// Commit the package.json and CHANGELOG.md files to git (if modified)
"@semantic-release/git",
{
assets: ["package.json", "CHANGELOG.md"].filter(Boolean),

View File

@@ -3,20 +3,22 @@
import os from "node:os"
import { massarg } from "massarg"
import chalk from "chalk"
import { LogLevel, ScaffoldCmdConfig } from "./types"
import { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
import path from "node:path"
import fs from "node:fs/promises"
import { parseAppendData, parseConfigFile } from "./config"
import { getConfigFile, parseAppendData, parseConfigFile } from "./config"
import { log } from "./logger"
import { MassargCommand } from "massarg/command"
export async function parseCliArgs(args = process.argv.slice(2)) {
const isProjectRoot = Boolean(await fs.stat(path.join(__dirname, "package.json")).catch(() => false))
const pkgFile = await fs.readFile(path.resolve(__dirname, isProjectRoot ? "." : "..", "package.json"))
const pkg = JSON.parse(pkgFile.toString())
const isVersionFlag = args.includes("--version") || args.includes("-v")
const isConfigProvided =
args.includes("--config") || args.includes("-c") || args.includes("--git") || args.includes("-g") || isVersionFlag
const isConfigFileProvided = args.includes("--config") || args.includes("-c")
const isGitProvided = args.includes("--git") || args.includes("-g")
const isConfigProvided = isConfigFileProvided || isGitProvided || isVersionFlag
return massarg<ScaffoldCmdConfig>({
name: pkg.name,
@@ -46,24 +48,20 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
aliases: ["n"],
description:
"Name to be passed to the generated files. `{{name}}` and other data parameters inside " +
"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` for this specific option.",
"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` " +
"for this specific option.",
isDefault: true,
required: !isConfigProvided,
})
.option({
name: "config",
aliases: ["c"],
description:
"Filename to load config from instead of passing arguments to CLI or using a Node.js " +
"script. See examples for syntax. This can also work in conjunction with `--git` or `--github` to point " +
"to remote files, and with `--key` to denote which key to select from the file.",
description: "Filename or directory to load config from",
})
.option({
name: "git",
aliases: ["g"],
description:
"Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See " +
"examples for syntax.",
description: "Git URL or GitHub path to load a template from.",
})
.option({
name: "key",
@@ -159,6 +157,67 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
aliases: ["v"],
description: "Display version.",
})
.command(
new MassargCommand<ListCommandCliOptions>({
name: "list",
aliases: ["ls"],
description: "List all available templates for a given config. See `list -h` for more information.",
run: async (_config) => {
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
const config = {
templates: [],
name: "",
version: false,
output: "",
subdir: false,
overwrite: false,
dryRun: false,
..._config,
config: _config.config ?? (!_config.git ? process.cwd() : undefined),
}
try {
const file = await getConfigFile(config, tmpPath)
console.log(chalk.underline`Available templates:\n`)
console.log(Object.keys(file).join("\n"))
} catch (e) {
const message = "message" in (e as any) ? (e as any).message : e?.toString()
log(config, LogLevel.error, message)
} finally {
log(config, LogLevel.debug, "Cleaning up temporary files...", tmpPath)
await fs.rm(tmpPath, { recursive: true, force: true })
}
},
})
.option({
name: "config",
aliases: ["c"],
description: "Filename or directory to load config from. Defaults to current working directory.",
})
.option({
name: "git",
aliases: ["g"],
description: "Git URL or GitHub path to load a template from.",
})
.option({
name: "log-level",
aliases: ["l"],
defaultValue: LogLevel.none,
description:
"Determine amount of logs to display. The values are: " +
`${chalk.bold`\`none | debug | info | warn | error\``}. ` +
"The provided level will display messages of the same level or higher.",
parse: (v) => {
const val = v.toLowerCase()
if (!(val in LogLevel)) {
throw new Error(`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(", ")}`)
}
return val
},
})
.help({
bindOption: true,
}),
)
.example({
description: "Usage with config file",
input: "simple-scaffold -c scaffold.cmd.js --key component",

View File

@@ -10,6 +10,7 @@ import {
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigFile,
ScaffoldConfigMap,
} from "./types"
import { handlebarsParse } from "./parser"
import { log } from "./logger"
@@ -49,6 +50,34 @@ function isWrappedWithQuotes(string: string): boolean {
return (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'"))
}
/** @internal */
export async function getConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfigMap> {
if (config.git && !config.git.includes("://")) {
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
config.git = githubPartToUrl(config.git)
}
const isGit = Boolean(config.git)
const configFilename = config.config
const configPath = isGit ? config.git : configFilename
log(config, LogLevel.info, `Loading config from file ${configFilename}`)
const configPromise = await (isGit
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpPath })
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))
// resolve the config
let configImport = await resolve(configPromise, config)
// If the config is a function or promise, return the output
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
log(config, LogLevel.debug, "Config is a function or promise, resolving...")
configImport = await resolve(configImport.default, config)
}
return configImport
}
/** @internal */
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
let output: ScaffoldConfig = config
@@ -57,36 +86,14 @@ export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string
config.logLevel = LogLevel.none
}
if (config.git && !config.git.includes("://")) {
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
config.git = githubPartToUrl(config.git)
}
const shouldLoadConfig = config.config || config.git
const shouldLoadConfig = Boolean(config.config || config.git)
if (shouldLoadConfig) {
const isGit = Boolean(config.git)
const key = config.key ?? "default"
const configFilename = config.config
const configPath = isGit ? config.git : configFilename
log(config, LogLevel.info, `Loading config from file ${configFilename} with key ${key}`)
const configPromise = await (isGit
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpPath })
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))
// resolve the config
let configImport = await resolve(configPromise, config)
// If the config is a function or promise, return the output
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
log(config, LogLevel.debug, "Config is a function or promise, resolving...")
configImport = await resolve(configImport.default, config)
}
const configImport = await getConfigFile(config, tmpPath)
if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${configFilename}`)
throw new Error(`Template "${key}" not found in ${config.config}`)
}
const imported = configImport[key]

View File

@@ -413,3 +413,5 @@ export type RemoteConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config
/** @internal */
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">
export type ListCommandCliOptions = Pick<ScaffoldCmdConfig, "config" | "git" | "logLevel" | "quiet">