Skip to content

Commit 995b433

Browse files
committed
feat!: separate git/github/config flags
feat: separate git/github/config t test cleanup
1 parent 4821be6 commit 995b433

8 files changed

Lines changed: 87 additions & 114 deletions

File tree

docs/docs/usage/cli.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +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 or https git URL to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax. |
18-
| `--github` \| `-gh` | GitHub path 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 |
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+
| `--github`\|`-gh` | GitHub path to load config from instead of passing arguments to CLI or using a Node.js script. |
20+
| `--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`) |
21+
| `--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. |
22+
| `--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. |
23+
| `--overwrite` \| `-w` | Enable to override output files, even if they already exist. |
24+
| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
25+
| `--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` |
26+
| `--create-sub-folder` \| `-s` | Create subfolder with the input name |
27+
| `--sub-folder-name-helper` \| `-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
28+
| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`) |
29+
| `--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. |
30+
| `--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. |
31+
| `--help` \| `-h` | Show this help message |
3132

3233
## Examples:
3334

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"date-fns": "^3.3.1",
4040
"glob": "^10.3.10",
4141
"handlebars": "^4.7.8",
42-
"massarg": "2.0.0-pre.10"
42+
"massarg": "2.0.0-pre.11"
4343
},
4444
"devDependencies": {
4545
"@knodes/typedoc-plugin-pages": "^0.23.4",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,16 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
3535
name: "config",
3636
aliases: ["c"],
3737
description:
38-
"Filename or https git URL to load config from instead of passing arguments to CLI or using a Node.js " +
39-
"script. See examples for syntax.",
38+
"Filename to load config from instead of passing arguments to CLI or using a Node.js " +
39+
"script. See examples for syntax. This can also work in conjunction with `--git` or `--github` to point " +
40+
"to remote files, and with `--key` to denote which key to select from the file.",
41+
})
42+
.option({
43+
name: "git",
44+
aliases: ["g"],
45+
description:
46+
"Git URL to load config from instead of passing arguments to CLI or using a Node.js script. See " +
47+
"examples for syntax.",
4048
})
4149
.option({
4250
name: "github",

src/config.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FileResponseHandler,
66
LogConfig,
77
LogLevel,
8+
RemoteConfigLoadConfig,
89
ScaffoldCmdConfig,
910
ScaffoldConfig,
1011
ScaffoldConfigFile,
@@ -14,6 +15,7 @@ import { log } from "./logger"
1415
import { resolve, wrapNoopResolver } from "./utils"
1516
import { getGitConfig } from "./git"
1617

18+
/** @internal */
1719
export function getOptionValueForFile<T>(
1820
config: ScaffoldConfig,
1921
filePath: string,
@@ -30,6 +32,7 @@ export function getOptionValueForFile<T>(
3032
)
3133
}
3234

35+
/** @internal */
3336
export function parseAppendData(value: string, options: ScaffoldCmdConfig): unknown {
3437
const data = options.data ?? {}
3538
const [key, val] = value.split(/\:?=/)
@@ -46,34 +49,42 @@ function isWrappedWithQuotes(string: string): boolean {
4649

4750
/** @internal */
4851
export async function parseConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfig> {
49-
let c: ScaffoldConfig = config
52+
let output: ScaffoldConfig = config
5053

5154
if (config.quiet) {
5255
config.logLevel = LogLevel.none
5356
}
5457

5558
if (config.github) {
5659
log(config, LogLevel.info, `Loading config from github ${config.github}`)
57-
config.config = githubPartToUrl(config.github)
60+
config.git = githubPartToUrl(config.github)
5861
}
5962

60-
if (config.config) {
61-
const { configFile, key, isRemote } = parseConfigSelection(config.config, config.key)
63+
if (config.config || config.git) {
64+
const isGit = Boolean(config.git)
65+
const key = config.key ?? "default"
66+
const configFile = config.config
67+
const loadPath = isGit ? config.git : configFile
68+
6269
log(config, LogLevel.info, `Loading config from ${configFile} with key ${key}`)
63-
const configPromise = await getConfig({
64-
config: configFile,
65-
isRemote,
66-
logLevel: config.logLevel,
67-
})
70+
const configPromise = await (config.git
71+
? getRemoteConfig({ git: loadPath, config: configFile, logLevel: config.logLevel })
72+
: getLocalConfig({ config: configFile, logLevel: config.logLevel }))
73+
74+
// resolve the config
6875
let configImport = await resolve(configPromise, config)
76+
77+
// If the config is a function or promise, return the output
6978
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
7079
configImport = await resolve(configImport.default, config)
7180
}
81+
7282
if (!configImport[key]) {
7383
throw new Error(`Template "${key}" not found in ${configFile}`)
7484
}
85+
7586
const importedKey = configImport[key]
76-
c = {
87+
output = {
7788
...config,
7889
...importedKey,
7990
data: {
@@ -83,20 +94,12 @@ export async function parseConfigFile(config: ScaffoldCmdConfig): Promise<Scaffo
8394
}
8495
}
8596

86-
c.data = { ...c.data, ...config.appendData }
97+
output.data = { ...output.data, ...config.appendData }
8798
delete config.appendData
88-
return c
89-
}
90-
91-
export function parseConfigSelection(
92-
config: string,
93-
key?: string,
94-
): { configFile: string; key: string; isRemote: boolean } {
95-
const isUrl = config.includes("://")
96-
const _key = key || "default"
97-
return { configFile: config, key: _key, isRemote: isUrl }
99+
return output
98100
}
99101

102+
/** @internal */
100103
export function githubPartToUrl(part: string): string {
101104
const gitUrl = new URL(`https://github.com/${part}`)
102105
if (!gitUrl.pathname.endsWith(".git")) {
@@ -106,26 +109,29 @@ export function githubPartToUrl(part: string): string {
106109
}
107110

108111
/** @internal */
109-
export async function getConfig(config: ConfigLoadConfig & Partial<LogConfig>): Promise<ScaffoldConfigFile> {
110-
const { config: configFile, isRemote, ...logConfig } = config as Required<typeof config>
112+
export async function getLocalConfig(config: ConfigLoadConfig & Partial<LogConfig>): Promise<ScaffoldConfigFile> {
113+
const { config: configFile, ...logConfig } = config as Required<typeof config>
111114

112-
if (!isRemote) {
113-
log(logConfig, LogLevel.info, `Loading config from file ${configFile}`)
114-
const absolutePath = path.resolve(process.cwd(), configFile)
115-
return wrapNoopResolver(import(absolutePath))
116-
}
115+
log(logConfig, LogLevel.info, `Loading config from file ${configFile}`)
116+
const absolutePath = path.resolve(process.cwd(), configFile)
117+
return wrapNoopResolver(import(absolutePath))
118+
}
119+
120+
/** @internal */
121+
export async function getRemoteConfig(
122+
config: RemoteConfigLoadConfig & Partial<LogConfig>,
123+
): Promise<ScaffoldConfigFile> {
124+
const { config: configFile, git, ...logConfig } = config as Required<typeof config>
125+
126+
log(logConfig, LogLevel.info, `Loading config from remote ${git}, file ${configFile}`)
117127

118-
const url = new URL(configFile)
128+
const url = new URL(git!)
119129
const isHttp = url.protocol === "http:" || url.protocol === "https:"
120130
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
121131

122-
if (isGit) {
123-
return getGitConfig(url, logConfig)
124-
}
125-
126-
if (!isHttp) {
132+
if (!isGit) {
127133
throw new Error(`Unsupported protocol ${url.protocol}`)
128134
}
129135

130-
return wrapNoopResolver(import(path.resolve(process.cwd(), configFile)))
136+
return getGitConfig(url, configFile, logConfig)
131137
}

src/git.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { resolve, wrapNoopResolver } from "./utils"
77

88
export async function getGitConfig(
99
url: URL,
10+
file: string,
1011
logConfig: LogConfig,
1112
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
1213
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
@@ -22,8 +23,9 @@ export async function getGitConfig(
2223
clone.on("close", async (code) => {
2324
if (code === 0) {
2425
log(logConfig, LogLevel.info, `Loading config from git repo: ${repoUrl}`)
25-
const hashPath = url.hash?.replace("#", "") || "scaffold.config.js"
26-
const absolutePath = path.resolve(tmpPath, hashPath)
26+
// TODO search for dynamic config file in repo if not provided
27+
const filename = file || "scaffold.config.js"
28+
const absolutePath = path.resolve(tmpPath, filename)
2729
const loadedConfig = await resolve(
2830
async () => (await import(absolutePath)).default as ScaffoldConfigMap,
2931
logConfig,

src/types.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,6 @@ export type FileResponse<T> = T | FileResponseHandler<T>
322322

323323
/** @internal */
324324
export interface ScaffoldCmdConfig {
325-
/**
326-
* Name to be passed to the generated files. `{{name}}` and `{{Name}}` inside contents and file names will be replaced
327-
* accordingly.
328-
*/
329325
name: string
330326
templates: string[]
331327
output: string
@@ -337,8 +333,8 @@ export interface ScaffoldCmdConfig {
337333
logLevel: LogLevel
338334
dryRun: boolean
339335
config?: string
340-
/** The key to use for the file which contains the template configurations. */
341336
key?: string
337+
git?: string
342338
github?: string
343339
}
344340

@@ -372,9 +368,11 @@ export type AsyncResolver<T, R = T> = Resolver<T, Promise<R> | R>
372368
/** @internal */
373369
export type LogConfig = Pick<ScaffoldConfig, "logLevel">
374370

375-
// TODO deprecat isRemote
376371
/** @internal */
377-
export type ConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config"> & { isRemote: boolean }
372+
export type ConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config">
373+
374+
/** @internal */
375+
export type RemoteConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config" | "git">
378376

379377
/** @internal */
380378
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">

0 commit comments

Comments
 (0)