mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-17 17:28:09 +00:00
feat: list command
This commit is contained in:
@@ -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.
|
To see this and more information anytime, add the `-h` or `--help` flag to your call, e.g.
|
||||||
`npx simple-scaffold@latest -h`.
|
`npx simple-scaffold@latest -h`.
|
||||||
|
|
||||||
| Command \| alias | |
|
| 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. |
|
| `--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., |
|
| `--config`\|`-c` | Filename or directory to load config from |
|
||||||
| `--git`\|`-g` | Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax. |
|
| `--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`) |
|
| `--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. |
|
| `--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. |
|
| `--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. |
|
| `--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. |
|
| `--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` |
|
| `--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 |
|
| `--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`. |
|
| `--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`) |
|
| `--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. |
|
| `--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. |
|
| `--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 |
|
| `--help` \| `-h` | Show this help message |
|
||||||
| `--version` \| `-v` | Display version. |
|
| `--version` \| `-v` | Display version. |
|
||||||
|
|
||||||
## Examples:
|
## Examples:
|
||||||
|
|
||||||
|
|||||||
@@ -13,20 +13,14 @@ module.exports = {
|
|||||||
"@semantic-release/npm",
|
"@semantic-release/npm",
|
||||||
{
|
{
|
||||||
// only update the pkg version on root, don't publish
|
// only update the pkg version on root, don't publish
|
||||||
|
// this is to keep package.json version in sync with the release
|
||||||
npmPublish: false,
|
npmPublish: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// [
|
|
||||||
// '@semantic-release/npm',
|
|
||||||
// {
|
|
||||||
// // only update the pkg version on doc, don't publish
|
|
||||||
// npmPublish: false,
|
|
||||||
// pkgRoot: 'doc',
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
[
|
[
|
||||||
"@semantic-release/exec",
|
"@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"',
|
publishCmd: 'echo "Packing..."; cd ./dist && pnpm pack --pack-destination=../; echo "Done"',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -34,10 +28,12 @@ module.exports = {
|
|||||||
"@semantic-release/npm",
|
"@semantic-release/npm",
|
||||||
{
|
{
|
||||||
// publish from dist dir instead of root
|
// publish from dist dir instead of root
|
||||||
|
// this is the actual uild output
|
||||||
pkgRoot: "dist",
|
pkgRoot: "dist",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
// Release to GitHub
|
||||||
"@semantic-release/github",
|
"@semantic-release/github",
|
||||||
{
|
{
|
||||||
assets: ["*.tgz"],
|
assets: ["*.tgz"],
|
||||||
@@ -45,6 +41,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
branch === "master"
|
branch === "master"
|
||||||
? [
|
? [
|
||||||
|
// Update CHANGELOG.md only on master
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
{
|
{
|
||||||
changelogFile: "CHANGELOG.md",
|
changelogFile: "CHANGELOG.md",
|
||||||
@@ -53,6 +50,7 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
[
|
[
|
||||||
|
// Commit the package.json and CHANGELOG.md files to git (if modified)
|
||||||
"@semantic-release/git",
|
"@semantic-release/git",
|
||||||
{
|
{
|
||||||
assets: ["package.json", "CHANGELOG.md"].filter(Boolean),
|
assets: ["package.json", "CHANGELOG.md"].filter(Boolean),
|
||||||
|
|||||||
83
src/cmd.ts
83
src/cmd.ts
@@ -3,20 +3,22 @@
|
|||||||
import os from "node:os"
|
import os from "node:os"
|
||||||
import { massarg } from "massarg"
|
import { massarg } from "massarg"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import { LogLevel, ScaffoldCmdConfig } from "./types"
|
import { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig } from "./types"
|
||||||
import { Scaffold } from "./scaffold"
|
import { Scaffold } from "./scaffold"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import fs from "node:fs/promises"
|
import fs from "node:fs/promises"
|
||||||
import { parseAppendData, parseConfigFile } from "./config"
|
import { getConfigFile, parseAppendData, parseConfigFile } from "./config"
|
||||||
import { log } from "./logger"
|
import { log } from "./logger"
|
||||||
|
import { MassargCommand } from "massarg/command"
|
||||||
|
|
||||||
export async function parseCliArgs(args = process.argv.slice(2)) {
|
export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||||
const isProjectRoot = Boolean(await fs.stat(path.join(__dirname, "package.json")).catch(() => false))
|
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 pkgFile = await fs.readFile(path.resolve(__dirname, isProjectRoot ? "." : "..", "package.json"))
|
||||||
const pkg = JSON.parse(pkgFile.toString())
|
const pkg = JSON.parse(pkgFile.toString())
|
||||||
const isVersionFlag = args.includes("--version") || args.includes("-v")
|
const isVersionFlag = args.includes("--version") || args.includes("-v")
|
||||||
const isConfigProvided =
|
const isConfigFileProvided = args.includes("--config") || args.includes("-c")
|
||||||
args.includes("--config") || args.includes("-c") || args.includes("--git") || args.includes("-g") || isVersionFlag
|
const isGitProvided = args.includes("--git") || args.includes("-g")
|
||||||
|
const isConfigProvided = isConfigFileProvided || isGitProvided || isVersionFlag
|
||||||
|
|
||||||
return massarg<ScaffoldCmdConfig>({
|
return massarg<ScaffoldCmdConfig>({
|
||||||
name: pkg.name,
|
name: pkg.name,
|
||||||
@@ -46,24 +48,20 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
|||||||
aliases: ["n"],
|
aliases: ["n"],
|
||||||
description:
|
description:
|
||||||
"Name to be passed to the generated files. `{{name}}` and other data parameters inside " +
|
"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,
|
isDefault: true,
|
||||||
required: !isConfigProvided,
|
required: !isConfigProvided,
|
||||||
})
|
})
|
||||||
.option({
|
.option({
|
||||||
name: "config",
|
name: "config",
|
||||||
aliases: ["c"],
|
aliases: ["c"],
|
||||||
description:
|
description: "Filename or directory to load config from",
|
||||||
"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.",
|
|
||||||
})
|
})
|
||||||
.option({
|
.option({
|
||||||
name: "git",
|
name: "git",
|
||||||
aliases: ["g"],
|
aliases: ["g"],
|
||||||
description:
|
description: "Git URL or GitHub path to load a template from.",
|
||||||
"Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See " +
|
|
||||||
"examples for syntax.",
|
|
||||||
})
|
})
|
||||||
.option({
|
.option({
|
||||||
name: "key",
|
name: "key",
|
||||||
@@ -159,6 +157,67 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
|||||||
aliases: ["v"],
|
aliases: ["v"],
|
||||||
description: "Display version.",
|
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({
|
.example({
|
||||||
description: "Usage with config file",
|
description: "Usage with config file",
|
||||||
input: "simple-scaffold -c scaffold.cmd.js --key component",
|
input: "simple-scaffold -c scaffold.cmd.js --key component",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ScaffoldCmdConfig,
|
ScaffoldCmdConfig,
|
||||||
ScaffoldConfig,
|
ScaffoldConfig,
|
||||||
ScaffoldConfigFile,
|
ScaffoldConfigFile,
|
||||||
|
ScaffoldConfigMap,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import { handlebarsParse } from "./parser"
|
import { handlebarsParse } from "./parser"
|
||||||
import { log } from "./logger"
|
import { log } from "./logger"
|
||||||
@@ -49,6 +50,34 @@ function isWrappedWithQuotes(string: string): boolean {
|
|||||||
return (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'"))
|
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 */
|
/** @internal */
|
||||||
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
|
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
|
||||||
let output: ScaffoldConfig = config
|
let output: ScaffoldConfig = config
|
||||||
@@ -57,36 +86,14 @@ export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string
|
|||||||
config.logLevel = LogLevel.none
|
config.logLevel = LogLevel.none
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.git && !config.git.includes("://")) {
|
const shouldLoadConfig = Boolean(config.config || config.git)
|
||||||
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
|
|
||||||
config.git = githubPartToUrl(config.git)
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldLoadConfig = config.config || config.git
|
|
||||||
|
|
||||||
if (shouldLoadConfig) {
|
if (shouldLoadConfig) {
|
||||||
const isGit = Boolean(config.git)
|
|
||||||
const key = config.key ?? "default"
|
const key = config.key ?? "default"
|
||||||
const configFilename = config.config
|
const configImport = await getConfigFile(config, tmpPath)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configImport[key]) {
|
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]
|
const imported = configImport[key]
|
||||||
|
|||||||
@@ -413,3 +413,5 @@ export type RemoteConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">
|
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">
|
||||||
|
|
||||||
|
export type ListCommandCliOptions = Pick<ScaffoldCmdConfig, "config" | "git" | "logLevel" | "quiet">
|
||||||
|
|||||||
Reference in New Issue
Block a user