new-branch 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -17
- package/dist/cli.js +94 -11
- package/dist/config/loadConfig.js +35 -0
- package/dist/config/sources/git.loader.js +56 -0
- package/dist/config/sources/packageJson.loader.js +37 -0
- package/dist/config/sources/rc.loader.js +26 -0
- package/dist/config/types.js +8 -0
- package/dist/config/validate.js +85 -0
- package/dist/didactic/explain.js +67 -0
- package/dist/didactic/listTransforms.js +21 -0
- package/dist/didactic/printConfig.js +23 -0
- package/dist/git/gitBuiltins.js +3 -3
- package/dist/git/gitConfig.js +30 -2
- package/dist/parseArgs.js +15 -4
- package/dist/pattern/transforms/after.js +12 -0
- package/dist/pattern/transforms/before.js +12 -0
- package/dist/pattern/transforms/ifEmpty.js +12 -0
- package/dist/pattern/transforms/index.js +14 -0
- package/dist/pattern/transforms/remove.js +12 -0
- package/dist/pattern/transforms/replace.js +12 -0
- package/dist/pattern/transforms/replaceAll.js +12 -0
- package/dist/pattern/transforms/stripAccents.js +9 -0
- package/dist/runtime/resolveMissingValues.js +8 -2
- package/package.json +1 -4
- package/dist/config/loadProjectConfig.js +0 -19
package/README.md
CHANGED
|
@@ -177,31 +177,63 @@ Disable prompts with:
|
|
|
177
177
|
|
|
178
178
|
---
|
|
179
179
|
|
|
180
|
-
## Project Configuration
|
|
180
|
+
## Project Configuration and precedence
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
Configuration for `new-branch` may come from several places. The CLI resolves the first _non-empty_ configuration it finds according to the following precedence (highest → lowest):
|
|
183
|
+
|
|
184
|
+
1. CLI flags (explicit `--pattern`, `--type`, etc.)
|
|
185
|
+
2. `.newbranchrc.json` (a repository-local JSON config file)
|
|
186
|
+
3. `package.json` under the `new-branch` key
|
|
187
|
+
4. Git config (`new-branch.pattern`) — local then global
|
|
188
|
+
5. Interactive prompt (only if enabled and a value is still missing)
|
|
189
|
+
|
|
190
|
+
This means that if a higher-precedence source provides a non-empty value, lower-precedence sources are not consulted or merged.
|
|
191
|
+
|
|
192
|
+
Examples
|
|
193
|
+
|
|
194
|
+
1. `.newbranchrc.json` (preferred when present and non-empty):
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"pattern": "{type}/{title:slugify}-{id}",
|
|
199
|
+
"types": [
|
|
200
|
+
{ "value": "feat", "label": "Feature" },
|
|
201
|
+
{ "value": "fix", "label": "Fix" }
|
|
202
|
+
],
|
|
203
|
+
"defaultType": "feat"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
2. `package.json` fallback:
|
|
183
208
|
|
|
184
209
|
```json
|
|
185
210
|
{
|
|
186
211
|
"new-branch": {
|
|
187
|
-
"pattern": "{type}/{title:slugify}-{id}"
|
|
212
|
+
"pattern": "{type}/{title:slugify}-{id}",
|
|
213
|
+
"defaultType": "fix"
|
|
188
214
|
}
|
|
189
215
|
}
|
|
190
216
|
```
|
|
191
217
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
You can also define a default pattern using Git config:
|
|
218
|
+
3. Git config fallback (local takes precedence over global):
|
|
195
219
|
|
|
196
220
|
```bash
|
|
197
221
|
git config --local new-branch.pattern "{type}/{title:slugify}-{id}"
|
|
222
|
+
git config --global new-branch.pattern "{type}/{title:slugify}-{id}"
|
|
198
223
|
```
|
|
199
224
|
|
|
200
|
-
|
|
225
|
+
Notes about `type` and `defaultType`
|
|
201
226
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
- Order for resolving the branch `type` follows the SPEC behavior we implemented:
|
|
228
|
+
1. CLI `--type` (explicit flag) overrides everything.
|
|
229
|
+
2. `defaultType` from the selected configuration source is used next (if present).
|
|
230
|
+
3. If the project config declares exactly one `type` in `types[]`, that single type is used as a convenience.
|
|
231
|
+
4. If the type is still not resolved and interactive prompting is allowed, the CLI will prompt for it.
|
|
232
|
+
5. If the type is still missing and `--no-prompt` (or `prompt: false`) is in effect, the CLI will fail with a helpful error.
|
|
233
|
+
|
|
234
|
+
- Validation: when a configuration source provides both `types[]` and `defaultType`, the `defaultType` must match one of the declared `types[].value`. If it does not, configuration validation will surface an error.
|
|
235
|
+
|
|
236
|
+
- Interactive prompts: when `types[]` are present in the chosen project config, those entries are exposed as choices to the interactive `type` select prompt so users see and can pick project-defined types.
|
|
205
237
|
|
|
206
238
|
To remove the pattern from Git config:
|
|
207
239
|
|
|
@@ -213,13 +245,6 @@ git config --unset --local new-branch.pattern
|
|
|
213
245
|
git config --unset --global new-branch.pattern
|
|
214
246
|
```
|
|
215
247
|
|
|
216
|
-
When using Git config, the resolution order becomes:
|
|
217
|
-
|
|
218
|
-
1. CLI flags
|
|
219
|
-
2. `package.json` configuration
|
|
220
|
-
3. Git config (`new-branch.pattern`)
|
|
221
|
-
4. Interactive prompt (if enabled)
|
|
222
|
-
|
|
223
248
|
---
|
|
224
249
|
|
|
225
250
|
## Git Safety
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from "./parseArgs.js";
|
|
3
3
|
import { parsePattern } from "./pattern/parsePattern.js";
|
|
4
|
-
import { defaultTransforms } from "./pattern/transforms/index.js";
|
|
4
|
+
import { allTransforms, defaultTransforms } from "./pattern/transforms/index.js";
|
|
5
5
|
import { renderPattern } from "./pattern/transforms/renderPattern.js";
|
|
6
6
|
import { resolveMissingValues } from "./runtime/resolveMissingValues.js";
|
|
7
7
|
import { getBuiltinValues } from "./runtime/builtins.js";
|
|
8
|
-
import {
|
|
8
|
+
import { loadConfigWithSource } from "./config/loadConfig.js";
|
|
9
9
|
import { getGitConfig } from "./git/gitConfig.js";
|
|
10
10
|
import { extractGitBuiltinKeysFromPattern, getGitBuiltins, patternNeedsGitBuiltins, } from "./git/gitBuiltins.js";
|
|
11
11
|
import { sanitizeGitRef } from "./git/sanitizeGitRef.js";
|
|
12
12
|
import { validateBranchName } from "./git/validateBranchName.js";
|
|
13
13
|
import { createBranch } from "./git/createBranch.js";
|
|
14
|
+
import { listTransforms } from "./didactic/listTransforms.js";
|
|
15
|
+
import { printConfig } from "./didactic/printConfig.js";
|
|
16
|
+
import { explain } from "./didactic/explain.js";
|
|
14
17
|
const ok = (value) => ({ ok: true, value });
|
|
15
18
|
const err = (error) => ({ ok: false, error });
|
|
16
19
|
const isOk = (r) => r.ok;
|
|
@@ -50,27 +53,76 @@ function toInitialValues(args) {
|
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
export async function run() {
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// Normalize argv so it works consistently across:
|
|
57
|
+
// - node dist/cli.js --id 123
|
|
58
|
+
// - pnpm dev -- --id 123
|
|
59
|
+
// - tsx src/cli.ts --id 123
|
|
60
|
+
//
|
|
61
|
+
// Notes:
|
|
62
|
+
// - `pnpm` may inject a standalone "--" before the script flags.
|
|
63
|
+
// - `tsx` puts the script path (e.g. `src/cli.ts`) as the first item in `process.argv.slice(2)`.
|
|
64
|
+
// That script path is not a flag, so we strip leading non-flag arguments.
|
|
65
|
+
let argv = process.argv.slice(2);
|
|
66
|
+
// Strip leading positional entries like `src/cli.ts` (common when running via `tsx`).
|
|
67
|
+
while (argv.length > 0 && argv[0] !== "--" && !argv[0].startsWith("-")) {
|
|
68
|
+
argv = argv.slice(1);
|
|
69
|
+
}
|
|
70
|
+
// Strip standalone "--" injected by pnpm.
|
|
71
|
+
if (argv[0] === "--") {
|
|
72
|
+
argv = argv.slice(1);
|
|
73
|
+
}
|
|
58
74
|
const wantsHelp = argv.includes("--help") || argv.includes("-h");
|
|
59
|
-
|
|
75
|
+
// Important: parseArgs should receive the reconstructed argv
|
|
76
|
+
const args = parseArgs(["node", "cli", ...argv]);
|
|
60
77
|
if (wantsHelp) {
|
|
61
78
|
return;
|
|
62
79
|
}
|
|
80
|
+
// --- Didactic mode: --list-transforms ---
|
|
81
|
+
if (args.options.listTransforms) {
|
|
82
|
+
console.log(listTransforms(allTransforms));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
63
85
|
const quiet = args.options.quiet === true;
|
|
64
86
|
const create = args.options.create === true;
|
|
65
87
|
const prompt = args.options.prompt !== false;
|
|
88
|
+
const isExplain = args.options.explain === true;
|
|
66
89
|
// Pipeline: pattern -> AST -> resolve values -> render -> sanitize -> validate -> (optional) git -> output
|
|
67
|
-
const projectConfig = await
|
|
90
|
+
const { config: projectConfig, source: configSource } = await loadConfigWithSource();
|
|
91
|
+
// --- Didactic mode: --print-config ---
|
|
92
|
+
if (args.options.printConfig) {
|
|
93
|
+
console.log(printConfig(projectConfig, configSource));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
68
96
|
// Git config (respects local -> global precedence automatically)
|
|
69
97
|
let gitPattern;
|
|
70
|
-
if (!args.options.pattern && !projectConfig.pattern) {
|
|
98
|
+
if (!args.options.pattern && !args.options.use && !projectConfig.pattern) {
|
|
71
99
|
gitPattern = await getGitConfig("new-branch.pattern");
|
|
72
100
|
}
|
|
73
|
-
|
|
101
|
+
// Resolution logic for --use:
|
|
102
|
+
// If --pattern is provided, use it directly (highest precedence).
|
|
103
|
+
// If --use is provided, resolve from patterns config. Error if alias not found.
|
|
104
|
+
// Otherwise, fall back to configured pattern or git config.
|
|
105
|
+
let useAliasPattern;
|
|
106
|
+
if (!args.options.pattern && args.options.use) {
|
|
107
|
+
const aliasName = args.options.use;
|
|
108
|
+
const aliasPattern = projectConfig.patterns?.[aliasName];
|
|
109
|
+
if (!aliasPattern) {
|
|
110
|
+
fail(`Unknown pattern alias "${aliasName}". Available aliases: ${projectConfig.patterns
|
|
111
|
+
? Object.keys(projectConfig.patterns).join(", ")
|
|
112
|
+
: "(none configured)"}`);
|
|
113
|
+
}
|
|
114
|
+
useAliasPattern = aliasPattern;
|
|
115
|
+
}
|
|
116
|
+
const resolvedPattern = args.options.pattern ?? useAliasPattern ?? projectConfig.pattern ?? gitPattern;
|
|
117
|
+
const patternSource = args.options.pattern
|
|
118
|
+
? "CLI --pattern"
|
|
119
|
+
: useAliasPattern
|
|
120
|
+
? `CLI --use (${args.options.use})`
|
|
121
|
+
: projectConfig.pattern
|
|
122
|
+
? configSource
|
|
123
|
+
: gitPattern
|
|
124
|
+
? "git config"
|
|
125
|
+
: "(none)";
|
|
74
126
|
const patternRes = requirePattern(resolvedPattern);
|
|
75
127
|
if (!isOk(patternRes))
|
|
76
128
|
fail("Invalid CLI arguments.", patternRes.error);
|
|
@@ -89,13 +141,28 @@ export async function run() {
|
|
|
89
141
|
// GitBuiltins is compatible with RenderValues (string | undefined)
|
|
90
142
|
gitValues = gitRes.value;
|
|
91
143
|
}
|
|
144
|
+
// Resolve `type` from CLI or config, honoring precedence:
|
|
145
|
+
// 1. CLI --type overrides everything
|
|
146
|
+
// 2. projectConfig.defaultType (if present)
|
|
147
|
+
// 3. if only one type is declared, use that as a convenience
|
|
148
|
+
// 4. otherwise leave undefined so resolveMissingValues will prompt (if prompt===true)
|
|
149
|
+
let resolvedType = args.options.type ?? projectConfig.defaultType;
|
|
150
|
+
if (!resolvedType && projectConfig.types?.length === 1) {
|
|
151
|
+
resolvedType = projectConfig.types[0].value;
|
|
152
|
+
}
|
|
92
153
|
const initialValues = {
|
|
93
154
|
...builtinValues,
|
|
94
155
|
...gitValues,
|
|
95
156
|
...toInitialValues(args),
|
|
157
|
+
type: resolvedType,
|
|
96
158
|
};
|
|
97
159
|
const valuesRes = await safeAsync(() => resolveMissingValues(astRes.value, initialValues, {
|
|
98
160
|
prompt,
|
|
161
|
+
// If project config defines `types`, expose them as choices for the
|
|
162
|
+
// interactive `type` select so the user sees and can choose project values.
|
|
163
|
+
typeChoices: projectConfig.types
|
|
164
|
+
? projectConfig.types.map((t) => ({ name: t.label, value: t.value }))
|
|
165
|
+
: undefined,
|
|
99
166
|
}));
|
|
100
167
|
if (!isOk(valuesRes))
|
|
101
168
|
fail("Failed to resolve required values.", valuesRes.error);
|
|
@@ -106,6 +173,22 @@ export async function run() {
|
|
|
106
173
|
if (!isOk(renderedRes))
|
|
107
174
|
fail("Failed to render branch name.", renderedRes.error);
|
|
108
175
|
const sanitized = sanitizeGitRef(renderedRes.value);
|
|
176
|
+
// --- Didactic mode: --explain ---
|
|
177
|
+
if (isExplain) {
|
|
178
|
+
console.log(explain({
|
|
179
|
+
pattern: patternRes.value,
|
|
180
|
+
patternSource,
|
|
181
|
+
ast: astRes.value,
|
|
182
|
+
resolvedValues: valuesRes.value,
|
|
183
|
+
cliValues: toInitialValues(args),
|
|
184
|
+
builtinValues,
|
|
185
|
+
gitValues,
|
|
186
|
+
rendered: renderedRes.value,
|
|
187
|
+
sanitized,
|
|
188
|
+
transforms: defaultTransforms,
|
|
189
|
+
}));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
109
192
|
const validateRes = await safeAsync(() => validateBranchName(sanitized));
|
|
110
193
|
if (!isOk(validateRes))
|
|
111
194
|
fail("Branch name is not valid for git.", validateRes.error);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* Aggregates configuration sources without merging.
|
|
4
|
+
*
|
|
5
|
+
* Precedence:
|
|
6
|
+
* 1) .new-branchrc.json
|
|
7
|
+
* 2) package.json
|
|
8
|
+
* 3) git config
|
|
9
|
+
*/
|
|
10
|
+
import { rcLoader } from "./sources/rc.loader.js";
|
|
11
|
+
import { packageJsonLoader } from "./sources/packageJson.loader.js";
|
|
12
|
+
import { gitLoader } from "./sources/git.loader.js";
|
|
13
|
+
/**
|
|
14
|
+
* Loads the first configuration found.
|
|
15
|
+
* No merging is performed.
|
|
16
|
+
*/
|
|
17
|
+
export async function loadConfig() {
|
|
18
|
+
const { config } = await loadConfigWithSource();
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Loads the first configuration found and reports which source provided it.
|
|
23
|
+
*/
|
|
24
|
+
export async function loadConfigWithSource() {
|
|
25
|
+
const rcRes = await rcLoader.load();
|
|
26
|
+
if (rcRes.found && rcRes.config && Object.keys(rcRes.config).length > 0)
|
|
27
|
+
return { config: rcRes.config, source: ".newbranchrc.json" };
|
|
28
|
+
const pkgRes = await packageJsonLoader.load();
|
|
29
|
+
if (pkgRes.found && pkgRes.config && Object.keys(pkgRes.config).length > 0)
|
|
30
|
+
return { config: pkgRes.config, source: "package.json" };
|
|
31
|
+
const gitRes = await gitLoader.load();
|
|
32
|
+
if (gitRes.found)
|
|
33
|
+
return { config: gitRes.config ?? {}, source: "git config" };
|
|
34
|
+
return { config: {}, source: "(none)" };
|
|
35
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getGitConfig, getGitConfigRegexp } from "../../git/gitConfig.js";
|
|
2
|
+
import { validateProjectConfigSource, validateProjectConfigFinal } from "../validate.js";
|
|
3
|
+
function parseGitTypes(raw) {
|
|
4
|
+
return raw
|
|
5
|
+
.split(",")
|
|
6
|
+
.map((s) => s.trim())
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((entry) => {
|
|
9
|
+
const idx = entry.indexOf(":");
|
|
10
|
+
if (idx === -1) {
|
|
11
|
+
return { value: entry, label: entry };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
value: entry.slice(0, idx).trim(),
|
|
15
|
+
label: entry.slice(idx + 1).trim(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const PATTERNS_PREFIX = "new-branch.patterns.";
|
|
20
|
+
function parseGitPatterns(entries) {
|
|
21
|
+
if (entries.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, value] of entries) {
|
|
25
|
+
const name = key.slice(PATTERNS_PREFIX.length);
|
|
26
|
+
if (name && value) {
|
|
27
|
+
result[name] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
31
|
+
}
|
|
32
|
+
export const gitLoader = {
|
|
33
|
+
source: "git",
|
|
34
|
+
async load() {
|
|
35
|
+
const pattern = await getGitConfig("new-branch.pattern");
|
|
36
|
+
const defaultType = await getGitConfig("new-branch.defaultType");
|
|
37
|
+
const typesRaw = await getGitConfig("new-branch.types");
|
|
38
|
+
const patternsEntries = await getGitConfigRegexp("^new-branch\\.patterns\\.");
|
|
39
|
+
if (!pattern && !defaultType && !typesRaw && patternsEntries.length === 0) {
|
|
40
|
+
return { found: false, source: "git", config: undefined };
|
|
41
|
+
}
|
|
42
|
+
const cfg = {};
|
|
43
|
+
if (pattern)
|
|
44
|
+
cfg.pattern = pattern;
|
|
45
|
+
if (defaultType)
|
|
46
|
+
cfg.defaultType = defaultType;
|
|
47
|
+
if (typesRaw)
|
|
48
|
+
cfg.types = parseGitTypes(typesRaw);
|
|
49
|
+
const patterns = parseGitPatterns(patternsEntries);
|
|
50
|
+
if (patterns)
|
|
51
|
+
cfg.patterns = patterns;
|
|
52
|
+
const sourceValidated = validateProjectConfigSource(cfg, "git config");
|
|
53
|
+
const finalValidated = validateProjectConfigFinal(sourceValidated, "git config");
|
|
54
|
+
return { found: true, source: "git", config: finalValidated };
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { validateProjectConfigSource, validateProjectConfigFinal } from "../validate.js";
|
|
4
|
+
function isNodeFsError(e) {
|
|
5
|
+
return typeof e === "object" && e !== null && "code" in e;
|
|
6
|
+
}
|
|
7
|
+
export const packageJsonLoader = {
|
|
8
|
+
source: "package.json",
|
|
9
|
+
async load() {
|
|
10
|
+
try {
|
|
11
|
+
const path = join(process.cwd(), "package.json");
|
|
12
|
+
const raw = await readFile(path, "utf8");
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
15
|
+
return { found: false, source: "package.json", config: undefined };
|
|
16
|
+
}
|
|
17
|
+
const pkg = parsed;
|
|
18
|
+
const block = pkg["new-branch"];
|
|
19
|
+
if (!block) {
|
|
20
|
+
return { found: false, source: "package.json", config: undefined };
|
|
21
|
+
}
|
|
22
|
+
const sourceValidated = validateProjectConfigSource(block, "package.json");
|
|
23
|
+
const finalValidated = validateProjectConfigFinal(sourceValidated, "package.json");
|
|
24
|
+
return {
|
|
25
|
+
found: true,
|
|
26
|
+
source: "package.json",
|
|
27
|
+
config: finalValidated,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
if (isNodeFsError(e) && e.code === "ENOENT") {
|
|
32
|
+
return { found: false, source: "package.json", config: undefined };
|
|
33
|
+
}
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { validateProjectConfigSource, validateProjectConfigFinal } from "../validate.js";
|
|
4
|
+
export const RC_FILENAME = ".newbranchrc.json";
|
|
5
|
+
function isNodeFsError(e) {
|
|
6
|
+
return typeof e === "object" && e !== null && "code" in e;
|
|
7
|
+
}
|
|
8
|
+
export const rcLoader = {
|
|
9
|
+
source: "rc",
|
|
10
|
+
async load() {
|
|
11
|
+
try {
|
|
12
|
+
const path = join(process.cwd(), RC_FILENAME);
|
|
13
|
+
const raw = await readFile(path, "utf8");
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
const sourceValidated = validateProjectConfigSource(parsed, RC_FILENAME);
|
|
16
|
+
const finalValidated = validateProjectConfigFinal(sourceValidated, RC_FILENAME);
|
|
17
|
+
return { found: true, source: "rc", config: finalValidated };
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
if (isNodeFsError(e) && e.code === "ENOENT") {
|
|
21
|
+
return { found: false, source: "rc", config: undefined };
|
|
22
|
+
}
|
|
23
|
+
throw e;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* Validation + normalization logic for `new-branch` configuration.
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1) validateProjectConfigSource → structural validation per source
|
|
7
|
+
* 2) validateProjectConfigFinal → cross-field business rules
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Throws a standardized configuration error.
|
|
11
|
+
*/
|
|
12
|
+
function invariant(condition, source, message) {
|
|
13
|
+
if (!condition) {
|
|
14
|
+
throw new Error(`Invalid new-branch config from ${source}: ${message}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isObject(v) {
|
|
18
|
+
return typeof v === "object" && v !== null;
|
|
19
|
+
}
|
|
20
|
+
function isString(v) {
|
|
21
|
+
return typeof v === "string";
|
|
22
|
+
}
|
|
23
|
+
function trimOrUndefined(v) {
|
|
24
|
+
const t = v.trim();
|
|
25
|
+
return t.length > 0 ? t : undefined;
|
|
26
|
+
}
|
|
27
|
+
function normalizeBranchType(raw, source) {
|
|
28
|
+
invariant(isObject(raw), source, "types[] must be an object");
|
|
29
|
+
const obj = raw;
|
|
30
|
+
const value = trimOrUndefined(String(obj.value ?? ""));
|
|
31
|
+
const label = trimOrUndefined(String(obj.label ?? ""));
|
|
32
|
+
invariant(value, source, "types[].value cannot be empty");
|
|
33
|
+
invariant(label, source, "types[].label cannot be empty");
|
|
34
|
+
return { value, label };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Structural validation for a single source.
|
|
38
|
+
*/
|
|
39
|
+
export function validateProjectConfigSource(raw, source) {
|
|
40
|
+
invariant(isObject(raw), source, "config must be an object");
|
|
41
|
+
const obj = raw;
|
|
42
|
+
const cfg = {};
|
|
43
|
+
if ("pattern" in obj) {
|
|
44
|
+
invariant(isString(obj.pattern), source, "pattern must be a string");
|
|
45
|
+
cfg.pattern = trimOrUndefined(obj.pattern);
|
|
46
|
+
}
|
|
47
|
+
if ("defaultType" in obj) {
|
|
48
|
+
invariant(isString(obj.defaultType), source, "defaultType must be a string");
|
|
49
|
+
cfg.defaultType = trimOrUndefined(obj.defaultType);
|
|
50
|
+
}
|
|
51
|
+
if ("patterns" in obj) {
|
|
52
|
+
const patternsVal = obj.patterns;
|
|
53
|
+
invariant(isObject(patternsVal), source, "patterns must be an object");
|
|
54
|
+
const entries = Object.entries(patternsVal);
|
|
55
|
+
const normalized = {};
|
|
56
|
+
for (const [key, val] of entries) {
|
|
57
|
+
invariant(isString(val), source, `patterns["${key}"] must be a string`);
|
|
58
|
+
const trimmed = trimOrUndefined(val);
|
|
59
|
+
invariant(trimmed, source, `patterns["${key}"] cannot be empty`);
|
|
60
|
+
normalized[key] = trimmed;
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(normalized).length > 0) {
|
|
63
|
+
cfg.patterns = normalized;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if ("types" in obj) {
|
|
67
|
+
const typesVal = obj.types;
|
|
68
|
+
invariant(Array.isArray(typesVal), source, "types must be an array");
|
|
69
|
+
cfg.types = typesVal.map((t) => normalizeBranchType(t, source));
|
|
70
|
+
}
|
|
71
|
+
return cfg;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Final cross-field validation.
|
|
75
|
+
*/
|
|
76
|
+
export function validateProjectConfigFinal(cfg, source) {
|
|
77
|
+
if (cfg.types) {
|
|
78
|
+
invariant(cfg.types.length > 0, source, "types cannot be empty");
|
|
79
|
+
}
|
|
80
|
+
if (cfg.defaultType && cfg.types) {
|
|
81
|
+
const exists = cfg.types.some((t) => t.value === cfg.defaultType);
|
|
82
|
+
invariant(exists, source, `defaultType "${cfg.defaultType}" must exist in types`);
|
|
83
|
+
}
|
|
84
|
+
return cfg;
|
|
85
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produces a structured breakdown of the full branch-name pipeline.
|
|
3
|
+
*
|
|
4
|
+
* No branch is created — this is purely informational.
|
|
5
|
+
* Used by the `--explain` CLI flag.
|
|
6
|
+
*/
|
|
7
|
+
export function explain(input) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push("Pipeline explanation:\n");
|
|
10
|
+
// 1. Pattern
|
|
11
|
+
lines.push(` Pattern: ${input.pattern}`);
|
|
12
|
+
lines.push(` Pattern source: ${input.patternSource}`);
|
|
13
|
+
// 2. Variables used
|
|
14
|
+
const vars = input.ast.variablesUsed;
|
|
15
|
+
lines.push(`\n Variables used: ${vars.length > 0 ? vars.join(", ") : "(none)"}`);
|
|
16
|
+
// 3. Builtin values
|
|
17
|
+
const builtinEntries = Object.entries(input.builtinValues).filter(([, v]) => v !== undefined);
|
|
18
|
+
if (builtinEntries.length > 0) {
|
|
19
|
+
lines.push(`\n Builtin values:`);
|
|
20
|
+
for (const [k, v] of builtinEntries) {
|
|
21
|
+
lines.push(` ${k} = "${v}"`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// 4. Git values
|
|
25
|
+
const gitEntries = Object.entries(input.gitValues).filter(([, v]) => v !== undefined);
|
|
26
|
+
if (gitEntries.length > 0) {
|
|
27
|
+
lines.push(`\n Git values:`);
|
|
28
|
+
for (const [k, v] of gitEntries) {
|
|
29
|
+
lines.push(` ${k} = "${v}"`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// 5. CLI values
|
|
33
|
+
const cliEntries = Object.entries(input.cliValues).filter(([, v]) => v !== undefined);
|
|
34
|
+
if (cliEntries.length > 0) {
|
|
35
|
+
lines.push(`\n CLI values:`);
|
|
36
|
+
for (const [k, v] of cliEntries) {
|
|
37
|
+
lines.push(` ${k} = "${v}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// 6. Transforms applied — with intermediate values
|
|
41
|
+
const transformNodes = input.ast.nodes.filter((n) => n.kind === "variable" && n.transforms.length > 0);
|
|
42
|
+
if (transformNodes.length > 0) {
|
|
43
|
+
lines.push(`\n Transforms applied:`);
|
|
44
|
+
for (const node of transformNodes) {
|
|
45
|
+
if (node.kind !== "variable")
|
|
46
|
+
continue;
|
|
47
|
+
const initial = input.resolvedValues[node.name] ?? "";
|
|
48
|
+
lines.push(` {${node.name}} = "${initial}"`);
|
|
49
|
+
let current = initial;
|
|
50
|
+
for (const t of node.transforms) {
|
|
51
|
+
const fn = input.transforms[t.name];
|
|
52
|
+
if (fn) {
|
|
53
|
+
current = fn(current, t.args);
|
|
54
|
+
}
|
|
55
|
+
const argsStr = t.args.length > 0 ? `:${t.args.join(":")}` : "";
|
|
56
|
+
lines.push(` → ${t.name}${argsStr} → "${current}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 7. Final result
|
|
61
|
+
lines.push(`\n Rendered: ${input.rendered}`);
|
|
62
|
+
if (input.rendered !== input.sanitized) {
|
|
63
|
+
lines.push(` Sanitized: ${input.sanitized}`);
|
|
64
|
+
}
|
|
65
|
+
lines.push(`\n Final branch: ${input.sanitized}`);
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prints a formatted table of all available transforms.
|
|
3
|
+
*
|
|
4
|
+
* Each transform is listed with its name, summary and usage example.
|
|
5
|
+
* This is used by the `--list-transforms` CLI flag.
|
|
6
|
+
*/
|
|
7
|
+
export function listTransforms(transforms) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push("Available transforms:\n");
|
|
10
|
+
const maxName = Math.max(...transforms.map((t) => t.name.length));
|
|
11
|
+
for (const t of transforms) {
|
|
12
|
+
const name = t.name.padEnd(maxName + 2);
|
|
13
|
+
const summary = t.doc?.summary ?? "(no description)";
|
|
14
|
+
const usage = t.doc?.usage?.[0] ?? "";
|
|
15
|
+
lines.push(` ${name}${summary}`);
|
|
16
|
+
if (usage) {
|
|
17
|
+
lines.push(` ${"".padEnd(maxName + 2)}Example: ${usage}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats the resolved configuration for display.
|
|
3
|
+
*
|
|
4
|
+
* Shows the final resolved values from whichever source took precedence.
|
|
5
|
+
* This is used by the `--print-config` CLI flag.
|
|
6
|
+
*/
|
|
7
|
+
export function printConfig(config, source) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push("Resolved configuration:\n");
|
|
10
|
+
lines.push(` Source: ${source}`);
|
|
11
|
+
lines.push(` Pattern: ${config.pattern ?? "(not set)"}`);
|
|
12
|
+
lines.push(` Default type: ${config.defaultType ?? "(not set)"}`);
|
|
13
|
+
if (config.types && config.types.length > 0) {
|
|
14
|
+
lines.push(` Types:`);
|
|
15
|
+
for (const t of config.types) {
|
|
16
|
+
lines.push(` - ${t.value} (${t.label})`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push(` Types: (not configured)`);
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
package/dist/git/gitBuiltins.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { execa } from "execa";
|
|
2
1
|
import { getGitConfig } from "../git/gitConfig.js";
|
|
2
|
+
import { execa } from "execa";
|
|
3
3
|
export const GIT_BUILTIN_KEYS = [
|
|
4
4
|
"shortSha",
|
|
5
5
|
"currentBranch",
|
|
@@ -15,8 +15,8 @@ function pickAllKeysIfUndefined(keys) {
|
|
|
15
15
|
}
|
|
16
16
|
async function safeExec(args) {
|
|
17
17
|
try {
|
|
18
|
-
const { stdout } = await execa("git", args);
|
|
19
|
-
const value = stdout.trim();
|
|
18
|
+
const { stdout } = (await execa("git", args));
|
|
19
|
+
const value = String(stdout ?? "").trim();
|
|
20
20
|
return value.length ? value : undefined;
|
|
21
21
|
}
|
|
22
22
|
catch {
|
package/dist/git/gitConfig.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
export async function getGitConfig(key) {
|
|
3
3
|
try {
|
|
4
|
-
const { stdout } = await execa("git", ["config", "--get", key]);
|
|
5
|
-
const value = stdout.trim();
|
|
4
|
+
const { stdout } = (await execa("git", ["config", "--get", key]));
|
|
5
|
+
const value = String(stdout ?? "").trim();
|
|
6
6
|
return value.length ? value : undefined;
|
|
7
7
|
}
|
|
8
8
|
catch {
|
|
9
9
|
return undefined;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns all git config entries matching a key prefix using `--get-regexp`.
|
|
14
|
+
* Each entry is returned as a `[key, value]` tuple.
|
|
15
|
+
*
|
|
16
|
+
* Example: `getGitConfigRegexp("new-branch.patterns\\.")` returns
|
|
17
|
+
* `[["new-branch.patterns.hotfix", "hotfix/{id}"], ...]`
|
|
18
|
+
*/
|
|
19
|
+
export async function getGitConfigRegexp(pattern) {
|
|
20
|
+
try {
|
|
21
|
+
const { stdout } = (await execa("git", ["config", "--get-regexp", pattern]));
|
|
22
|
+
const raw = String(stdout ?? "").trim();
|
|
23
|
+
if (!raw.length)
|
|
24
|
+
return [];
|
|
25
|
+
return raw.split("\n").reduce((acc, line) => {
|
|
26
|
+
const idx = line.indexOf(" ");
|
|
27
|
+
if (idx === -1)
|
|
28
|
+
return acc;
|
|
29
|
+
const key = line.slice(0, idx);
|
|
30
|
+
const value = line.slice(idx + 1);
|
|
31
|
+
if (key && value)
|
|
32
|
+
acc.push([key, value]);
|
|
33
|
+
return acc;
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/parseArgs.js
CHANGED
|
@@ -10,25 +10,36 @@ export function parseArgs(argv = process.argv) {
|
|
|
10
10
|
const cli = cac("new-branch");
|
|
11
11
|
cli
|
|
12
12
|
.option("-p, --pattern <pattern>", "Branch pattern")
|
|
13
|
+
.option("--use <name>", "Use a named pattern alias from configuration")
|
|
13
14
|
.option("--id <id>", "Task id")
|
|
14
15
|
.option("--title <title>", "Task title")
|
|
15
16
|
.option("--type <type>", "Branch type")
|
|
16
17
|
.option("--create", "Create branch")
|
|
17
18
|
.option("--no-prompt", "Fail instead of prompting for missing values")
|
|
18
19
|
.option("--quiet", "Suppress non-essential output")
|
|
20
|
+
.option("--explain", "Show a detailed breakdown of the branch pipeline without creating a branch")
|
|
21
|
+
.option("--list-transforms", "List all available transforms")
|
|
22
|
+
.option("--print-config", "Print the resolved configuration")
|
|
19
23
|
.help();
|
|
20
24
|
const cleaned = stripDoubleDash(argv);
|
|
21
25
|
const parsed = cli.parse(cleaned);
|
|
22
26
|
const opts = parsed.options;
|
|
23
27
|
const options = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// Accept numeric or string-like values and coerce to string when present.
|
|
29
|
+
pattern: opts.pattern !== undefined ? String(opts.pattern) : undefined,
|
|
30
|
+
use: opts.use !== undefined ? String(opts.use) : undefined,
|
|
31
|
+
id: opts.id !== undefined ? String(opts.id) : undefined,
|
|
32
|
+
title: opts.title !== undefined ? String(opts.title) : undefined,
|
|
33
|
+
type: opts.type !== undefined ? String(opts.type) : undefined,
|
|
28
34
|
create: typeof opts.create === "boolean" ? opts.create : undefined,
|
|
35
|
+
// CAC provides `--no-prompt` as `noPrompt` normally, but the flag will
|
|
36
|
+
// also be available as `prompt` when parsed; keep boolean handling.
|
|
29
37
|
prompt: typeof opts.prompt === "boolean" ? opts.prompt : undefined,
|
|
30
38
|
quiet: typeof opts.quiet === "boolean" ? opts.quiet : undefined,
|
|
31
39
|
help: typeof opts.help === "boolean" ? opts.help : undefined,
|
|
40
|
+
explain: typeof opts.explain === "boolean" ? opts.explain : undefined,
|
|
41
|
+
listTransforms: typeof opts.listTransforms === "boolean" ? opts.listTransforms : undefined,
|
|
42
|
+
printConfig: typeof opts.printConfig === "boolean" ? opts.printConfig : undefined,
|
|
32
43
|
};
|
|
33
44
|
return {
|
|
34
45
|
options,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const after = {
|
|
2
|
+
name: "after",
|
|
3
|
+
fn: (value, [suffix]) => {
|
|
4
|
+
if (suffix === undefined)
|
|
5
|
+
throw new Error("after requires one argument: {var:after:suffix}");
|
|
6
|
+
return value === "" ? "" : value + suffix;
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Adds a suffix only if the value is not empty.",
|
|
10
|
+
usage: ["{title:after:-wip}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const before = {
|
|
2
|
+
name: "before",
|
|
3
|
+
fn: (value, [prefix]) => {
|
|
4
|
+
if (prefix === undefined)
|
|
5
|
+
throw new Error("before requires one argument: {var:before:prefix}");
|
|
6
|
+
return value === "" ? "" : prefix + value;
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Adds a prefix only if the value is not empty.",
|
|
10
|
+
usage: ["{title:before:hotfix-}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const ifEmpty = {
|
|
2
|
+
name: "ifEmpty",
|
|
3
|
+
fn: (value, [fallback]) => {
|
|
4
|
+
if (fallback === undefined)
|
|
5
|
+
throw new Error("ifEmpty requires one argument: {var:ifEmpty:fallback}");
|
|
6
|
+
return value === "" ? fallback : value;
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Provides a fallback value if the result is empty.",
|
|
10
|
+
usage: ["{title:slugify:ifEmpty:no-title}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -8,6 +8,13 @@ import { kebab } from "../../pattern/transforms/kebab.js";
|
|
|
8
8
|
import { snake } from "../../pattern/transforms/snake.js";
|
|
9
9
|
import { title } from "../../pattern/transforms/title.js";
|
|
10
10
|
import { words } from "../../pattern/transforms/words.js";
|
|
11
|
+
import { replace } from "../../pattern/transforms/replace.js";
|
|
12
|
+
import { replaceAll } from "../../pattern/transforms/replaceAll.js";
|
|
13
|
+
import { remove } from "../../pattern/transforms/remove.js";
|
|
14
|
+
import { stripAccents } from "../../pattern/transforms/stripAccents.js";
|
|
15
|
+
import { ifEmpty } from "../../pattern/transforms/ifEmpty.js";
|
|
16
|
+
import { before } from "../../pattern/transforms/before.js";
|
|
17
|
+
import { after } from "../../pattern/transforms/after.js";
|
|
11
18
|
export const allTransforms = [
|
|
12
19
|
lower,
|
|
13
20
|
upper,
|
|
@@ -18,5 +25,12 @@ export const allTransforms = [
|
|
|
18
25
|
snake,
|
|
19
26
|
title,
|
|
20
27
|
words,
|
|
28
|
+
replace,
|
|
29
|
+
replaceAll,
|
|
30
|
+
remove,
|
|
31
|
+
stripAccents,
|
|
32
|
+
ifEmpty,
|
|
33
|
+
before,
|
|
34
|
+
after,
|
|
21
35
|
];
|
|
22
36
|
export const defaultTransforms = buildRegistry(allTransforms);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const remove = {
|
|
2
|
+
name: "remove",
|
|
3
|
+
fn: (value, [target]) => {
|
|
4
|
+
if (target === undefined)
|
|
5
|
+
throw new Error("remove requires one argument: {var:remove:substring}");
|
|
6
|
+
return value.replaceAll(target, "");
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Removes all occurrences of a substring.",
|
|
10
|
+
usage: ["{title:remove:temp}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const replace = {
|
|
2
|
+
name: "replace",
|
|
3
|
+
fn: (value, [search, replacement]) => {
|
|
4
|
+
if (search === undefined || replacement === undefined)
|
|
5
|
+
throw new Error("replace requires two arguments: {var:replace:search:replacement}");
|
|
6
|
+
return value.replace(search, replacement);
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Replaces the first occurrence of a substring.",
|
|
10
|
+
usage: ["{title:replace:foo:bar}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const replaceAll = {
|
|
2
|
+
name: "replaceAll",
|
|
3
|
+
fn: (value, [search, replacement]) => {
|
|
4
|
+
if (search === undefined || replacement === undefined)
|
|
5
|
+
throw new Error("replaceAll requires two arguments: {var:replaceAll:search:replacement}");
|
|
6
|
+
return value.replaceAll(search, replacement);
|
|
7
|
+
},
|
|
8
|
+
doc: {
|
|
9
|
+
summary: "Replaces all occurrences of a substring.",
|
|
10
|
+
usage: ["{title:replaceAll:foo:bar}"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { input, select } from "@inquirer/prompts";
|
|
2
1
|
import { TYPE_CHOICES } from "../runtime/enums.js";
|
|
3
2
|
/**
|
|
4
3
|
* Resolves missing variable values required by a parsed pattern.
|
|
@@ -40,10 +39,17 @@ export async function resolveMissingValues(parsed, initialValues, opts) {
|
|
|
40
39
|
if (!opts.prompt) {
|
|
41
40
|
throw new Error(`Missing required value: "${name}"`);
|
|
42
41
|
}
|
|
42
|
+
// Lazily import the interactive prompts to avoid importing
|
|
43
|
+
// `@inquirer/prompts` at module load time. This prevents environments
|
|
44
|
+
// with incompatible Node.js versions from failing when the CLI is
|
|
45
|
+
// executed in non-interactive mode (e.g., `--no-prompt`).
|
|
46
|
+
const prompts = await import("@inquirer/prompts");
|
|
47
|
+
const { input, select } = prompts;
|
|
43
48
|
if (name === "type") {
|
|
49
|
+
const choices = opts.typeChoices ?? TYPE_CHOICES;
|
|
44
50
|
const selected = await select({
|
|
45
51
|
message: "Select branch type:",
|
|
46
|
-
choices
|
|
52
|
+
choices,
|
|
47
53
|
});
|
|
48
54
|
values[name] = selected;
|
|
49
55
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "new-branch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Generate and create standardized git branch names from a pattern.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
"bugs": {
|
|
43
43
|
"url": "https://github.com/teles/new-branch/issues"
|
|
44
44
|
},
|
|
45
|
-
"new-branch": {
|
|
46
|
-
"pattern": "{type}/{title:lower}-{id}"
|
|
47
|
-
},
|
|
48
45
|
"homepage": "https://github.com/teles/new-branch#readme",
|
|
49
46
|
"packageManager": "pnpm@10.22.0",
|
|
50
47
|
"devDependencies": {
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
export async function loadProjectConfig() {
|
|
4
|
-
try {
|
|
5
|
-
const path = join(process.cwd(), "package.json");
|
|
6
|
-
const raw = await readFile(path, "utf8");
|
|
7
|
-
const pkg = JSON.parse(raw);
|
|
8
|
-
const config = pkg["new-branch"];
|
|
9
|
-
if (!config || typeof config !== "object")
|
|
10
|
-
return {};
|
|
11
|
-
const cfg = config;
|
|
12
|
-
return {
|
|
13
|
-
pattern: typeof cfg.pattern === "string" ? cfg.pattern : undefined,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
}
|