Skip to content

Commit d579c09

Browse files
committed
feat: list command
1 parent 3765398 commit d579c09

File tree

5 files changed

+129
-63
lines changed

5 files changed

+129
-63
lines changed

docs/docs/usage/03-cli.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ Usage: simple-scaffold [options]
1111
To see this and more information anytime, add the `-h` or `--help` flag to your call, e.g.
1212
`npx simple-scaffold@latest -h`.
1313

14-
| Command \| alias | |
15-
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16-
| `--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. |
17-
| `--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., |
18-
| `--git`\|`-g` | Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax. |
19-
| `--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`) |
20-
| `--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. |
21-
| `--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. |
22-
| `--overwrite` \| `-w` | Enable to override output files, even if they already exist. |
23-
| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
24-
| `--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` |
25-
| `--create-sub-folder` \| `-s` | Create subfolder with the input name |
26-
| `--sub-folder-name-helper` \| `-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
27-
| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`) |
28-
| `--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. |
29-
| `--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. |
30-
| `--help` \| `-h` | Show this help message |
31-
| `--version` \| `-v` | Display version. |
14+
| Command \| alias | |
15+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16+
| `--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. |
17+
| `--config`\|`-c` | Filename or directory to load config from |
18+
| `--git`\|`-g` | Git URL or GitHub path to load a template from. |
19+
| `--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`) |
20+
| `--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. |
21+
| `--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. |
22+
| `--overwrite` \| `-w` | Enable to override output files, even if they already exist. |
23+
| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
24+
| `--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` |
25+
| `--create-sub-folder` \| `-s` | Create subfolder with the input name |
26+
| `--sub-folder-name-helper` \| `-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
27+
| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`) |
28+
| `--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. |
29+
| `--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. |
30+
| `--help` \| `-h` | Show this help message |
31+
| `--version` \| `-v` | Display version. |
3232

3333
## Examples:
3434

release.config.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,35 @@ module.exports = {
1313
"@semantic-release/npm",
1414
{
1515
// only update the pkg version on root, don't publish
16+
// this is to keep package.json version in sync with the release
1617
npmPublish: false,
1718
},
1819
],
19-
// [
20-
// '@semantic-release/npm',
21-
// {
22-
// // only update the pkg version on doc, don't publish
23-
// npmPublish: false,
24-
// pkgRoot: 'doc',
25-
// },
26-
// ]
2720
[
2821
"@semantic-release/exec",
2922
{
23+
// pack the dist folder, during publish step (after version was bumped)
3024
publishCmd: 'echo "Packing..."; cd ./dist && pnpm pack --pack-destination=../; echo "Done"',
3125
},
3226
],
3327
[
3428
"@semantic-release/npm",
3529
{
3630
// publish from dist dir instead of root
31+
// this is the actual uild output
3732
pkgRoot: "dist",
3833
},
3934
],
4035
[
36+
// Release to GitHub
4137
"@semantic-release/github",
4238
{
4339
assets: ["*.tgz"],
4440
},
4541
],
4642
branch === "master"
4743
? [
44+
// Update CHANGELOG.md only on master
4845
"@semantic-release/changelog",
4946
{
5047
changelogFile: "CHANGELOG.md",
@@ -53,6 +50,7 @@ module.exports = {
5350
]
5451
: undefined,
5552
[
53+
// Commit the package.json and CHANGELOG.md files to git (if modified)
5654
"@semantic-release/git",
5755
{
5856
assets: ["package.json", "CHANGELOG.md"].filter(Boolean),

src/cmd.ts

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
import os from "node:os"
44
import { massarg } from "massarg"
55
import chalk from "chalk"
6-
import { LogLevel, ScaffoldCmdConfig } from "./types"
6+
import { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig } from "./types"
77
import { Scaffold } from "./scaffold"
88
import path from "node:path"
99
import fs from "node:fs/promises"
10-
import { parseAppendData, parseConfigFile } from "./config"
10+
import { getConfigFile, parseAppendData, parseConfigFile } from "./config"
1111
import { log } from "./logger"
12+
import { MassargCommand } from "massarg/command"
1213

1314
export async function parseCliArgs(args = process.argv.slice(2)) {
1415
const isProjectRoot = Boolean(await fs.stat(path.join(__dirname, "package.json")).catch(() => false))
1516
const pkgFile = await fs.readFile(path.resolve(__dirname, isProjectRoot ? "." : "..", "package.json"))
1617
const pkg = JSON.parse(pkgFile.toString())
1718
const isVersionFlag = args.includes("--version") || args.includes("-v")
18-
const isConfigProvided =
19-
args.includes("--config") || args.includes("-c") || args.includes("--git") || args.includes("-g") || isVersionFlag
19+
const isConfigFileProvided = args.includes("--config") || args.includes("-c")
20+
const isGitProvided = args.includes("--git") || args.includes("-g")
21+
const isConfigProvided = isConfigFileProvided || isGitProvided || isVersionFlag
2022

2123
return massarg<ScaffoldCmdConfig>({
2224
name: pkg.name,
@@ -46,24 +48,20 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
4648
aliases: ["n"],
4749
description:
4850
"Name to be passed to the generated files. `{{name}}` and other data parameters inside " +
49-
"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` for this specific option.",
51+
"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` " +
52+
"for this specific option.",
5053
isDefault: true,
5154
required: !isConfigProvided,
5255
})
5356
.option({
5457
name: "config",
5558
aliases: ["c"],
56-
description:
57-
"Filename to load config from instead of passing arguments to CLI or using a Node.js " +
58-
"script. See examples for syntax. This can also work in conjunction with `--git` or `--github` to point " +
59-
"to remote files, and with `--key` to denote which key to select from the file.",
59+
description: "Filename or directory to load config from",
6060
})
6161
.option({
6262
name: "git",
6363
aliases: ["g"],
64-
description:
65-
"Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See " +
66-
"examples for syntax.",
64+
description: "Git URL or GitHub path to load a template from.",
6765
})
6866
.option({
6967
name: "key",
@@ -159,6 +157,67 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
159157
aliases: ["v"],
160158
description: "Display version.",
161159
})
160+
.command(
161+
new MassargCommand<ListCommandCliOptions>({
162+
name: "list",
163+
aliases: ["ls"],
164+
description: "List all available templates for a given config. See `list -h` for more information.",
165+
run: async (_config) => {
166+
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
167+
const config = {
168+
templates: [],
169+
name: "",
170+
version: false,
171+
output: "",
172+
subdir: false,
173+
overwrite: false,
174+
dryRun: false,
175+
..._config,
176+
config: _config.config ?? (!_config.git ? process.cwd() : undefined),
177+
}
178+
try {
179+
const file = await getConfigFile(config, tmpPath)
180+
console.log(chalk.underline`Available templates:\n`)
181+
console.log(Object.keys(file).join("\n"))
182+
} catch (e) {
183+
const message = "message" in (e as any) ? (e as any).message : e?.toString()
184+
log(config, LogLevel.error, message)
185+
} finally {
186+
log(config, LogLevel.debug, "Cleaning up temporary files...", tmpPath)
187+
await fs.rm(tmpPath, { recursive: true, force: true })
188+
}
189+
},
190+
})
191+
.option({
192+
name: "config",
193+
aliases: ["c"],
194+
description: "Filename or directory to load config from. Defaults to current working directory.",
195+
})
196+
.option({
197+
name: "git",
198+
aliases: ["g"],
199+
description: "Git URL or GitHub path to load a template from.",
200+
})
201+
.option({
202+
name: "log-level",
203+
aliases: ["l"],
204+
defaultValue: LogLevel.none,
205+
description:
206+
"Determine amount of logs to display. The values are: " +
207+
`${chalk.bold`\`none | debug | info | warn | error\``}. ` +
208+
"The provided level will display messages of the same level or higher.",
209+
parse: (v) => {
210+
const val = v.toLowerCase()
211+
if (!(val in LogLevel)) {
212+
throw new Error(`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(", ")}`)
213+
}
214+
return val
215+
},
216+
})
217+
.help({
218+
bindOption: true,
219+
}),
220+
)
162221
.example({
163222
description: "Usage with config file",
164223
input: "simple-scaffold -c scaffold.cmd.js --key component",

src/config.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ScaffoldCmdConfig,
1111
ScaffoldConfig,
1212
ScaffoldConfigFile,
13+
ScaffoldConfigMap,
1314
} from "./types"
1415
import { handlebarsParse } from "./parser"
1516
import { log } from "./logger"
@@ -50,43 +51,49 @@ function isWrappedWithQuotes(string: string): boolean {
5051
}
5152

5253
/** @internal */
53-
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
54-
let output: ScaffoldConfig = config
55-
56-
if (config.quiet) {
57-
config.logLevel = LogLevel.none
58-
}
59-
54+
export async function getConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfigMap> {
6055
if (config.git && !config.git.includes("://")) {
6156
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
6257
config.git = githubPartToUrl(config.git)
6358
}
6459

65-
const shouldLoadConfig = config.config || config.git
60+
const isGit = Boolean(config.git)
61+
const configFilename = config.config
62+
const configPath = isGit ? config.git : configFilename
6663

67-
if (shouldLoadConfig) {
68-
const isGit = Boolean(config.git)
69-
const key = config.key ?? "default"
70-
const configFilename = config.config
71-
const configPath = isGit ? config.git : configFilename
64+
log(config, LogLevel.info, `Loading config from file ${configFilename}`)
7265

73-
log(config, LogLevel.info, `Loading config from file ${configFilename} with key ${key}`)
66+
const configPromise = await (isGit
67+
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpPath })
68+
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))
7469

75-
const configPromise = await (isGit
76-
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpPath })
77-
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))
70+
// resolve the config
71+
let configImport = await resolve(configPromise, config)
7872

79-
// resolve the config
80-
let configImport = await resolve(configPromise, config)
73+
// If the config is a function or promise, return the output
74+
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
75+
log(config, LogLevel.debug, "Config is a function or promise, resolving...")
76+
configImport = await resolve(configImport.default, config)
77+
}
78+
return configImport
79+
}
8180

82-
// If the config is a function or promise, return the output
83-
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
84-
log(config, LogLevel.debug, "Config is a function or promise, resolving...")
85-
configImport = await resolve(configImport.default, config)
86-
}
81+
/** @internal */
82+
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
83+
let output: ScaffoldConfig = config
84+
85+
if (config.quiet) {
86+
config.logLevel = LogLevel.none
87+
}
88+
89+
const shouldLoadConfig = Boolean(config.config || config.git)
90+
91+
if (shouldLoadConfig) {
92+
const key = config.key ?? "default"
93+
const configImport = await getConfigFile(config, tmpPath)
8794

8895
if (!configImport[key]) {
89-
throw new Error(`Template "${key}" not found in ${configFilename}`)
96+
throw new Error(`Template "${key}" not found in ${config.config}`)
9097
}
9198

9299
const imported = configImport[key]

0 commit comments

Comments
 (0)