new-branch 0.7.0 → 0.8.1
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 +27 -211
- package/dist/cli.js +92 -2
- package/dist/config/loadConfig.js +17 -7
- package/dist/config/sources/git.loader.js +27 -0
- package/dist/config/sources/packageJson.loader.js +13 -0
- package/dist/config/sources/rc.loader.js +15 -0
- package/dist/config/types.js +6 -4
- package/dist/config/validate.js +61 -8
- package/dist/didactic/explain.js +29 -2
- package/dist/didactic/listTransforms.js +7 -3
- package/dist/didactic/printConfig.js +8 -3
- package/dist/git/createBranch.js +10 -3
- package/dist/git/gitBuiltins.js +73 -6
- package/dist/git/gitConfig.js +28 -4
- package/dist/git/sanitizeGitRef.js +23 -5
- package/dist/git/truncateEnd.js +31 -0
- package/dist/git/validateBranchName.js +12 -3
- package/dist/parseArgs.js +34 -0
- package/dist/pattern/parsePattern.js +14 -0
- package/dist/pattern/transforms/after.js +15 -0
- package/dist/pattern/transforms/before.js +15 -0
- package/dist/pattern/transforms/camel.js +13 -7
- package/dist/pattern/transforms/helpers/words.js +6 -0
- package/dist/pattern/transforms/ifEmpty.js +15 -0
- package/dist/pattern/transforms/index.js +10 -0
- package/dist/pattern/transforms/kebab.js +10 -4
- package/dist/pattern/transforms/lower.js +13 -0
- package/dist/pattern/transforms/max.js +15 -0
- package/dist/pattern/transforms/registry.js +13 -0
- package/dist/pattern/transforms/remove.js +13 -0
- package/dist/pattern/transforms/renderPattern.js +9 -0
- package/dist/pattern/transforms/replace.js +13 -0
- package/dist/pattern/transforms/replaceAll.js +13 -0
- package/dist/pattern/transforms/slugify.js +18 -0
- package/dist/pattern/transforms/snake.js +10 -4
- package/dist/pattern/transforms/stripAccents.js +15 -0
- package/dist/pattern/transforms/title.js +11 -4
- package/dist/pattern/transforms/types.js +5 -0
- package/dist/pattern/transforms/upper.js +13 -0
- package/dist/pattern/transforms/words.js +11 -7
- package/dist/pattern/types.js +5 -0
- package/dist/runtime/builtins.js +5 -0
- package/dist/runtime/enums.js +14 -0
- package/package.json +6 -2
package/dist/config/validate.js
CHANGED
|
@@ -1,29 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* Validation + normalization logic for `new-branch` configuration.
|
|
2
|
+
* @module config/validate
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Validation and normalisation logic for `new-branch` configuration.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The validation pipeline consists of two stages:
|
|
8
|
+
* 1. {@link validateProjectConfigSource} — structural validation per source.
|
|
9
|
+
* 2. {@link validateProjectConfigFinal} — cross-field business rules.
|
|
8
10
|
*/
|
|
9
11
|
/**
|
|
10
|
-
* Throws a
|
|
12
|
+
* Throws a standardised configuration error when `condition` is falsy.
|
|
13
|
+
*
|
|
14
|
+
* @param condition - The value to assert.
|
|
15
|
+
* @param source - Label identifying the config source (for error messages).
|
|
16
|
+
* @param message - Human-readable description of the violated rule.
|
|
11
17
|
*/
|
|
12
18
|
function invariant(condition, source, message) {
|
|
13
19
|
if (!condition) {
|
|
14
20
|
throw new Error(`Invalid new-branch config from ${source}: ${message}`);
|
|
15
21
|
}
|
|
16
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Checks whether `v` is a non-null object.
|
|
25
|
+
*
|
|
26
|
+
* @param v - The value to check.
|
|
27
|
+
* @returns `true` if `v` is an object and not `null`.
|
|
28
|
+
*/
|
|
17
29
|
function isObject(v) {
|
|
18
30
|
return typeof v === "object" && v !== null;
|
|
19
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Type guard for strings.
|
|
34
|
+
*
|
|
35
|
+
* @param v - The value to check.
|
|
36
|
+
* @returns `true` if `v` is a `string`.
|
|
37
|
+
*/
|
|
20
38
|
function isString(v) {
|
|
21
39
|
return typeof v === "string";
|
|
22
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Trims a string and returns `undefined` when the result is empty.
|
|
43
|
+
*
|
|
44
|
+
* @param v - The string to trim.
|
|
45
|
+
* @returns The trimmed string, or `undefined` if blank.
|
|
46
|
+
*/
|
|
23
47
|
function trimOrUndefined(v) {
|
|
24
48
|
const t = v.trim();
|
|
25
49
|
return t.length > 0 ? t : undefined;
|
|
26
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Normalises a raw branch-type entry into a validated {@link BranchType}.
|
|
53
|
+
*
|
|
54
|
+
* @param raw - The unknown value to normalise.
|
|
55
|
+
* @param source - Config source label (for error messages).
|
|
56
|
+
* @returns A valid {@link BranchType}.
|
|
57
|
+
* @throws {@link Error} If `value` or `label` is missing/empty.
|
|
58
|
+
*/
|
|
27
59
|
function normalizeBranchType(raw, source) {
|
|
28
60
|
invariant(isObject(raw), source, "types[] must be an object");
|
|
29
61
|
const obj = raw;
|
|
@@ -34,7 +66,17 @@ function normalizeBranchType(raw, source) {
|
|
|
34
66
|
return { value, label };
|
|
35
67
|
}
|
|
36
68
|
/**
|
|
37
|
-
*
|
|
69
|
+
* Performs structural validation for a single configuration source.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Validates shapes but does **not** check cross-field rules
|
|
73
|
+
* (e.g. `defaultType` existing in `types`). Use
|
|
74
|
+
* {@link validateProjectConfigFinal} for that.
|
|
75
|
+
*
|
|
76
|
+
* @param raw - The raw config object to validate.
|
|
77
|
+
* @param source - Config source label (for error messages).
|
|
78
|
+
* @returns A validated {@link ProjectConfig}.
|
|
79
|
+
* @throws {@link Error} On any structural violation.
|
|
38
80
|
*/
|
|
39
81
|
export function validateProjectConfigSource(raw, source) {
|
|
40
82
|
invariant(isObject(raw), source, "config must be an object");
|
|
@@ -71,7 +113,18 @@ export function validateProjectConfigSource(raw, source) {
|
|
|
71
113
|
return cfg;
|
|
72
114
|
}
|
|
73
115
|
/**
|
|
74
|
-
*
|
|
116
|
+
* Performs cross-field (semantic) validation on an already-structurally-valid config.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* Rules enforced:
|
|
120
|
+
* - `types`, when present, must not be empty.
|
|
121
|
+
* - `defaultType`, when present alongside `types`, must match one of
|
|
122
|
+
* the declared type values.
|
|
123
|
+
*
|
|
124
|
+
* @param cfg - The structurally-valid config to check.
|
|
125
|
+
* @param source - Config source label (for error messages).
|
|
126
|
+
* @returns The same config if all rules pass.
|
|
127
|
+
* @throws {@link Error} On any semantic violation.
|
|
75
128
|
*/
|
|
76
129
|
export function validateProjectConfigFinal(cfg, source) {
|
|
77
130
|
if (cfg.types) {
|
package/dist/didactic/explain.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Produces a structured breakdown of the full
|
|
2
|
+
* Produces a structured, human-readable breakdown of the full
|
|
3
|
+
* branch-name pipeline.
|
|
3
4
|
*
|
|
5
|
+
* @remarks
|
|
4
6
|
* No branch is created — this is purely informational.
|
|
5
7
|
* Used by the `--explain` CLI flag.
|
|
8
|
+
*
|
|
9
|
+
* Sections printed:
|
|
10
|
+
* 1. Pattern and source
|
|
11
|
+
* 2. Variables used
|
|
12
|
+
* 3. Builtin values
|
|
13
|
+
* 4. Git values
|
|
14
|
+
* 5. CLI values
|
|
15
|
+
* 6. Transform chain with intermediate values
|
|
16
|
+
* 7. Rendered / Sanitized output
|
|
17
|
+
* 8. Max-length truncation (when applicable)
|
|
18
|
+
* 9. Final branch name
|
|
19
|
+
*
|
|
20
|
+
* @param input - The collected pipeline data.
|
|
21
|
+
* @returns A multi-line string ready to be printed to stdout.
|
|
6
22
|
*/
|
|
7
23
|
export function explain(input) {
|
|
8
24
|
const lines = [];
|
|
@@ -62,6 +78,17 @@ export function explain(input) {
|
|
|
62
78
|
if (input.rendered !== input.sanitized) {
|
|
63
79
|
lines.push(` Sanitized: ${input.sanitized}`);
|
|
64
80
|
}
|
|
65
|
-
|
|
81
|
+
// 8. Max-length truncation
|
|
82
|
+
if (input.maxLength !== undefined) {
|
|
83
|
+
lines.push(` Max length: ${input.maxLength}`);
|
|
84
|
+
if (input.truncated !== undefined && input.truncated !== input.sanitized) {
|
|
85
|
+
lines.push(` Truncated: ${input.truncated}`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
lines.push(` Truncated: (no truncation needed)`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const finalName = input.truncated ?? input.sanitized;
|
|
92
|
+
lines.push(`\n Final branch: ${finalName}`);
|
|
66
93
|
return lines.join("\n");
|
|
67
94
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Formats a human-readable table of all available transforms.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Each transform is listed with its name, summary, and an optional
|
|
6
|
+
* usage example. Used by the `--list-transforms` CLI flag.
|
|
7
|
+
*
|
|
8
|
+
* @param transforms - The ordered list of {@link TransformDef} definitions.
|
|
9
|
+
* @returns A multi-line formatted string ready to be printed to stdout.
|
|
6
10
|
*/
|
|
7
11
|
export function listTransforms(transforms) {
|
|
8
12
|
const lines = [];
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Formats the resolved configuration for display.
|
|
2
|
+
* Formats the resolved project configuration for display.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Shows the final resolved values from whichever source took
|
|
6
|
+
* precedence. Used by the `--print-config` CLI flag.
|
|
7
|
+
*
|
|
8
|
+
* @param config - The resolved {@link ProjectConfig}.
|
|
9
|
+
* @param source - Human-readable label identifying the winning source.
|
|
10
|
+
* @returns A multi-line formatted string ready to be printed to stdout.
|
|
6
11
|
*/
|
|
7
12
|
export function printConfig(config, source) {
|
|
8
13
|
const lines = [];
|
package/dist/git/createBranch.js
CHANGED
|
@@ -2,11 +2,18 @@ import { execa } from "execa";
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates and switches to a new Git branch.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Uses a two-step strategy for maximum compatibility:
|
|
6
7
|
* 1. Try `git switch -c <name>` (modern Git ≥ 2.23).
|
|
7
|
-
* 2. Fallback to `git checkout -b <name>` if switch is unavailable.
|
|
8
|
+
* 2. Fallback to `git checkout -b <name>` if `switch` is unavailable.
|
|
8
9
|
*
|
|
9
|
-
* @
|
|
10
|
+
* @param name - The name of the branch to create.
|
|
11
|
+
* @throws {@link Error} If both `git switch -c` and `git checkout -b` fail.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* await createBranch("feat/my-feature");
|
|
16
|
+
* ```
|
|
10
17
|
*/
|
|
11
18
|
export async function createBranch(name) {
|
|
12
19
|
try {
|
package/dist/git/gitBuiltins.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getGitConfig } from "../git/gitConfig.js";
|
|
2
2
|
import { execa } from "execa";
|
|
3
|
+
/**
|
|
4
|
+
* Ordered list of all supported {@link GitBuiltinKey} values.
|
|
5
|
+
*/
|
|
3
6
|
export const GIT_BUILTIN_KEYS = [
|
|
4
7
|
"shortSha",
|
|
5
8
|
"currentBranch",
|
|
@@ -7,12 +10,30 @@ export const GIT_BUILTIN_KEYS = [
|
|
|
7
10
|
"repoName",
|
|
8
11
|
"lastTag",
|
|
9
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* Deduplicates an array of {@link GitBuiltinKey} values.
|
|
15
|
+
*
|
|
16
|
+
* @param keys - Array of keys that may contain duplicates.
|
|
17
|
+
* @returns A new array with unique keys only.
|
|
18
|
+
*/
|
|
10
19
|
function uniqueKeys(keys) {
|
|
11
20
|
return Array.from(new Set(keys));
|
|
12
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the requested keys, or all supported keys when none are specified.
|
|
24
|
+
*
|
|
25
|
+
* @param keys - Optional subset of keys to resolve.
|
|
26
|
+
* @returns The keys to resolve (deduplicated).
|
|
27
|
+
*/
|
|
13
28
|
function pickAllKeysIfUndefined(keys) {
|
|
14
29
|
return keys?.length ? uniqueKeys(keys) : GIT_BUILTIN_KEYS;
|
|
15
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Executes a git command and returns its trimmed stdout, or `undefined` on failure.
|
|
33
|
+
*
|
|
34
|
+
* @param args - Arguments to pass to the `git` command.
|
|
35
|
+
* @returns The trimmed stdout output, or `undefined` if the command fails or produces no output.
|
|
36
|
+
*/
|
|
16
37
|
async function safeExec(args) {
|
|
17
38
|
try {
|
|
18
39
|
const { stdout } = (await execa("git", args));
|
|
@@ -23,10 +44,23 @@ async function safeExec(args) {
|
|
|
23
44
|
return undefined;
|
|
24
45
|
}
|
|
25
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Derives the repository name from the absolute path to the repo root.
|
|
49
|
+
*
|
|
50
|
+
* @param repoRoot - Absolute path returned by `git rev-parse --show-toplevel`.
|
|
51
|
+
* @returns The last segment of the path, or `undefined` if the path is empty.
|
|
52
|
+
*/
|
|
26
53
|
function deriveRepoName(repoRoot) {
|
|
27
54
|
const parts = repoRoot.split(/[\\/]/).filter(Boolean);
|
|
28
55
|
return parts.length ? parts[parts.length - 1] : undefined;
|
|
29
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolver map for each supported {@link GitBuiltinKey}.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Each resolver is an async function that returns the resolved value
|
|
62
|
+
* or `undefined` when unavailable.
|
|
63
|
+
*/
|
|
30
64
|
const RESOLVERS = {
|
|
31
65
|
shortSha: () => safeExec(["rev-parse", "--short", "HEAD"]),
|
|
32
66
|
currentBranch: async () => {
|
|
@@ -43,6 +77,12 @@ const RESOLVERS = {
|
|
|
43
77
|
return value ?? process.env.USER ?? process.env.USERNAME ?? undefined;
|
|
44
78
|
},
|
|
45
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Internal resolver that resolves the requested git builtin keys in parallel.
|
|
82
|
+
*
|
|
83
|
+
* @param keys - Optional subset of keys to resolve. When omitted, all keys are resolved.
|
|
84
|
+
* @returns An object containing the resolved values.
|
|
85
|
+
*/
|
|
46
86
|
async function resolveGitBuiltins(keys) {
|
|
47
87
|
const wanted = pickAllKeysIfUndefined(keys);
|
|
48
88
|
const entries = await Promise.all(wanted.map(async (key) => {
|
|
@@ -57,22 +97,49 @@ async function resolveGitBuiltins(keys) {
|
|
|
57
97
|
/**
|
|
58
98
|
* Resolves git-based built-in variables.
|
|
59
99
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* Each variable maps to a git command executed in the current repository.
|
|
102
|
+
* Resolution is done in parallel for performance.
|
|
103
|
+
*
|
|
104
|
+
* @param keys - Optional subset of {@link GitBuiltinKey} values to resolve.
|
|
105
|
+
* When omitted, all supported git builtins are resolved.
|
|
106
|
+
* @returns A promise resolving to a {@link GitBuiltins} object.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* // Resolve only what is needed
|
|
111
|
+
* const builtins = await getGitBuiltins(["shortSha", "currentBranch"]);
|
|
112
|
+
* builtins.shortSha; // "abc1234"
|
|
113
|
+
* builtins.currentBranch; // "main"
|
|
114
|
+
* ```
|
|
62
115
|
*/
|
|
63
116
|
export async function getGitBuiltins(keys) {
|
|
64
117
|
return resolveGitBuiltins(keys);
|
|
65
118
|
}
|
|
66
119
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
120
|
+
* Checks whether a pattern string references at least one git builtin key.
|
|
121
|
+
*
|
|
122
|
+
* @remarks
|
|
123
|
+
* Use this before calling {@link getGitBuiltins} to avoid spawning
|
|
124
|
+
* unnecessary git subprocesses.
|
|
125
|
+
*
|
|
126
|
+
* @param pattern - The raw pattern string to check.
|
|
127
|
+
* @returns `true` if the pattern contains at least one {@link GitBuiltinKey}.
|
|
69
128
|
*/
|
|
70
129
|
export function patternNeedsGitBuiltins(pattern) {
|
|
71
130
|
return GIT_BUILTIN_KEYS.some((key) => pattern.includes(key));
|
|
72
131
|
}
|
|
73
132
|
/**
|
|
74
|
-
* Extracts which
|
|
75
|
-
*
|
|
133
|
+
* Extracts which {@link GitBuiltinKey} values are present in a pattern.
|
|
134
|
+
*
|
|
135
|
+
* @param pattern - The raw pattern string to inspect.
|
|
136
|
+
* @returns An array of {@link GitBuiltinKey} values found in the pattern.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* extractGitBuiltinKeysFromPattern("{currentBranch}-{shortSha}");
|
|
141
|
+
* // => ["shortSha", "currentBranch"]
|
|
142
|
+
* ```
|
|
76
143
|
*/
|
|
77
144
|
export function extractGitBuiltinKeysFromPattern(pattern) {
|
|
78
145
|
return GIT_BUILTIN_KEYS.filter((key) => pattern.includes(key));
|
package/dist/git/gitConfig.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves a single Git config value by key.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Runs `git config --get <key>` as a subprocess. Returns `undefined`
|
|
7
|
+
* when the key is not set or the command fails (e.g. outside a repo).
|
|
8
|
+
*
|
|
9
|
+
* @param key - The Git config key to look up (e.g. `"user.name"`).
|
|
10
|
+
* @returns The trimmed config value, or `undefined` if not found.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const name = await getGitConfig("user.name"); // "Alice" | undefined
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
2
17
|
export async function getGitConfig(key) {
|
|
3
18
|
try {
|
|
4
19
|
const { stdout } = (await execa("git", ["config", "--get", key]));
|
|
@@ -10,11 +25,20 @@ export async function getGitConfig(key) {
|
|
|
10
25
|
}
|
|
11
26
|
}
|
|
12
27
|
/**
|
|
13
|
-
* Returns all git config entries matching a key
|
|
14
|
-
*
|
|
28
|
+
* Returns all git config entries matching a key pattern using `--get-regexp`.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Each entry is returned as a `[key, value]` tuple. Returns an empty
|
|
32
|
+
* array if no entries match or if the command fails.
|
|
33
|
+
*
|
|
34
|
+
* @param pattern - A regular expression pattern passed to `git config --get-regexp`.
|
|
35
|
+
* @returns An array of `[key, value]` tuples.
|
|
15
36
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const entries = await getGitConfigRegexp("^new-branch\\.patterns\\.");
|
|
40
|
+
* // => [["new-branch.patterns.hotfix", "hotfix/{id}"], ...]
|
|
41
|
+
* ```
|
|
18
42
|
*/
|
|
19
43
|
export async function getGitConfigRegexp(pattern) {
|
|
20
44
|
try {
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Performs a lightweight sanitization
|
|
3
|
-
* full validation to Git.
|
|
2
|
+
* Performs a lightweight sanitization of a Git ref name.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This function applies a set of heuristic cleanups to make the input
|
|
6
|
+
* more likely to pass `git check-ref-format --branch`. It does **not**
|
|
7
|
+
* fully reimplement all Git ref rules — final validation must still be
|
|
8
|
+
* delegated to Git itself via {@link validateBranchName}.
|
|
9
|
+
*
|
|
10
|
+
* Sanitization steps:
|
|
11
|
+
* 1. Trim whitespace and replace internal whitespace with `-`.
|
|
12
|
+
* 2. Remove characters forbidden by Git (`~ ^ : ? * [ ] \`).
|
|
13
|
+
* 3. Remove `@{` sequences.
|
|
14
|
+
* 4. Collapse multiple slashes and repeated dots.
|
|
15
|
+
* 5. Strip leading dashes/slashes and trailing slashes/dots.
|
|
16
|
+
* 6. Remove `.lock` suffix.
|
|
17
|
+
*
|
|
18
|
+
* @param input - The raw ref name to sanitize.
|
|
19
|
+
* @returns The sanitized ref name.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* sanitizeGitRef("feat/My Feature!!"); // => "feat/My-Feature"
|
|
24
|
+
* sanitizeGitRef("--leading/slash"); // => "leading/slash"
|
|
25
|
+
* ```
|
|
8
26
|
*/
|
|
9
27
|
export function sanitizeGitRef(input) {
|
|
10
28
|
let name = input.trim();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates a string from the end to ensure it does not exceed `maxLength`.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This is intentionally simple and deterministic:
|
|
6
|
+
* - If the string length is `<= maxLength`, return it unchanged.
|
|
7
|
+
* - Otherwise, cut from the end.
|
|
8
|
+
*
|
|
9
|
+
* Used by the `--max-length` CLI option after sanitization and before
|
|
10
|
+
* branch-name validation.
|
|
11
|
+
*
|
|
12
|
+
* @param value - The string to truncate.
|
|
13
|
+
* @param maxLength - The maximum allowed length. Must be a positive integer (`>= 1`).
|
|
14
|
+
* @returns The (possibly truncated) string.
|
|
15
|
+
* @throws {@link Error} If `maxLength` is not a positive integer.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* truncateEnd("feat/my-feature", 10); // => "feat/my-fe"
|
|
20
|
+
* truncateEnd("short", 100); // => "short"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function truncateEnd(value, maxLength) {
|
|
24
|
+
if (!Number.isInteger(maxLength) || maxLength < 1) {
|
|
25
|
+
throw new Error(`--max-length must be a positive integer (>= 1), got "${maxLength}".`);
|
|
26
|
+
}
|
|
27
|
+
if (value.length <= maxLength) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return value.slice(0, maxLength);
|
|
31
|
+
}
|
|
@@ -2,10 +2,19 @@ import { execa } from "execa";
|
|
|
2
2
|
/**
|
|
3
3
|
* Validates a branch name by delegating to Git itself.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Invokes `git check-ref-format --branch <name>` as a subprocess.
|
|
7
|
+
* This ensures the name complies with all rules enforced by the
|
|
8
|
+
* installed version of Git.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* @param name - The branch name to validate.
|
|
11
|
+
* @throws {@link Error} If the branch name does not pass `git check-ref-format`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* await validateBranchName("feat/my-branch"); // resolves
|
|
16
|
+
* await validateBranchName(".."); // throws
|
|
17
|
+
* ```
|
|
9
18
|
*/
|
|
10
19
|
export async function validateBranchName(name) {
|
|
11
20
|
try {
|
package/dist/parseArgs.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module parseArgs
|
|
3
|
+
*
|
|
4
|
+
* CLI argument parser built on top of {@link https://github.com/cacjs/cac | cac}.
|
|
5
|
+
* Defines every flag accepted by `new-branch` and coerces raw `process.argv`
|
|
6
|
+
* values into a strongly-typed {@link ParsedArgs} object.
|
|
7
|
+
*/
|
|
1
8
|
import { cac } from "cac";
|
|
9
|
+
/**
|
|
10
|
+
* Remove the bare `"--"` separator from an argv array so that `cac` does not
|
|
11
|
+
* choke on it. Everything before and after the separator is preserved.
|
|
12
|
+
*
|
|
13
|
+
* @param argv - Raw argument vector (typically `process.argv`).
|
|
14
|
+
* @returns A new array with the `"--"` element removed, if present.
|
|
15
|
+
*/
|
|
2
16
|
function stripDoubleDash(argv) {
|
|
3
17
|
const idx = argv.indexOf("--");
|
|
4
18
|
if (idx === -1)
|
|
@@ -6,6 +20,24 @@ function stripDoubleDash(argv) {
|
|
|
6
20
|
// keep node + script, remove the "--" separator and retain the rest
|
|
7
21
|
return [...argv.slice(0, idx), ...argv.slice(idx + 1)];
|
|
8
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse a raw argument vector into a typed {@link ParsedArgs} object.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* String-like option values are coerced via `String()` and numeric ones via
|
|
28
|
+
* `Number()`, so callers always receive the expected primitive types regardless
|
|
29
|
+
* of how the shell delivers them.
|
|
30
|
+
*
|
|
31
|
+
* @param argv - The argument vector to parse (defaults to `process.argv`).
|
|
32
|
+
* @returns Parsed and coerced CLI arguments.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const { options } = parseArgs(["node", "new-branch", "--type", "feat", "--id", "42"]);
|
|
37
|
+
* // options.type === "feat"
|
|
38
|
+
* // options.id === "42"
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
9
41
|
export function parseArgs(argv = process.argv) {
|
|
10
42
|
const cli = cac("new-branch");
|
|
11
43
|
cli
|
|
@@ -20,6 +52,7 @@ export function parseArgs(argv = process.argv) {
|
|
|
20
52
|
.option("--explain", "Show a detailed breakdown of the branch pipeline without creating a branch")
|
|
21
53
|
.option("--list-transforms", "List all available transforms")
|
|
22
54
|
.option("--print-config", "Print the resolved configuration")
|
|
55
|
+
.option("-L, --max-length <n>", "Maximum length for the final branch name")
|
|
23
56
|
.help();
|
|
24
57
|
const cleaned = stripDoubleDash(argv);
|
|
25
58
|
const parsed = cli.parse(cleaned);
|
|
@@ -40,6 +73,7 @@ export function parseArgs(argv = process.argv) {
|
|
|
40
73
|
explain: typeof opts.explain === "boolean" ? opts.explain : undefined,
|
|
41
74
|
listTransforms: typeof opts.listTransforms === "boolean" ? opts.listTransforms : undefined,
|
|
42
75
|
printConfig: typeof opts.printConfig === "boolean" ? opts.printConfig : undefined,
|
|
76
|
+
maxLength: opts.maxLength !== undefined ? Number(opts.maxLength) : undefined,
|
|
43
77
|
};
|
|
44
78
|
return {
|
|
45
79
|
options,
|
|
@@ -121,6 +121,14 @@ export function parsePattern(input) {
|
|
|
121
121
|
variablesUsed: uniquePreserveOrder(variablesUsed),
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Parses a single transform segment like `"slugify"` or `"max:25"`
|
|
126
|
+
* into a {@link TransformNode}.
|
|
127
|
+
*
|
|
128
|
+
* @param segment - The raw transform string (e.g. `"replace:_:-"`).
|
|
129
|
+
* @returns The parsed {@link TransformNode}.
|
|
130
|
+
* @throws {@link Error} If the segment is empty or has no name.
|
|
131
|
+
*/
|
|
124
132
|
function parseTransform(segment) {
|
|
125
133
|
// segment examples:
|
|
126
134
|
// - "slugify"
|
|
@@ -134,6 +142,12 @@ function parseTransform(segment) {
|
|
|
134
142
|
}
|
|
135
143
|
return { name, args };
|
|
136
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Deduplicates a string array while preserving the original order.
|
|
147
|
+
*
|
|
148
|
+
* @param items - The array of strings to deduplicate.
|
|
149
|
+
* @returns A new array containing only the first occurrence of each item.
|
|
150
|
+
*/
|
|
137
151
|
function uniquePreserveOrder(items) {
|
|
138
152
|
const seen = new Set();
|
|
139
153
|
const out = [];
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform: **after**
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Appends a suffix to the value. If the value is empty, returns
|
|
6
|
+
* an empty string (the suffix is not added to avoid dangling separators).
|
|
7
|
+
*
|
|
8
|
+
* Pattern syntax: `{variable:after:suffix}`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* after.fn("feat", ["-wip"]); // => "feat-wip"
|
|
13
|
+
* after.fn("", ["-wip"]); // => ""
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
1
16
|
export const after = {
|
|
2
17
|
name: "after",
|
|
3
18
|
fn: (value, [suffix]) => {
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform: **before**
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Prepends a prefix to the value. If the value is empty, returns
|
|
6
|
+
* an empty string (the prefix is not added to avoid dangling separators).
|
|
7
|
+
*
|
|
8
|
+
* Pattern syntax: `{variable:before:prefix}`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* before.fn("fix", ["hotfix-"]); // => "hotfix-fix"
|
|
13
|
+
* before.fn("", ["hotfix-"]); // => ""
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
1
16
|
export const before = {
|
|
2
17
|
name: "before",
|
|
3
18
|
fn: (value, [prefix]) => {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { splitWords, upperFirst } from "./helpers/words.js";
|
|
2
2
|
/**
|
|
3
|
-
* Transform: camel
|
|
3
|
+
* Transform: **camel**
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Converts an input string into camelCase. Uses {@link splitWords} to
|
|
7
|
+
* extract word boundaries and lower-cases the words before joining.
|
|
8
|
+
* The first word is kept in lower-case; subsequent words are
|
|
9
|
+
* capitalised with {@link upperFirst}.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Pattern syntax: `{variable:camel}`
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* camel.fn("My Task", []); // => "myTask"
|
|
16
|
+
* camel.fn("HTTP Server", []); // => "httpServer"
|
|
17
|
+
* ```
|
|
12
18
|
*/
|
|
13
19
|
export const camel = {
|
|
14
20
|
name: "camel",
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform: **ifEmpty**
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Provides a fallback value when the current value is an empty string.
|
|
6
|
+
* Useful for guaranteeing a non-empty segment in the branch name.
|
|
7
|
+
*
|
|
8
|
+
* Pattern syntax: `{variable:ifEmpty:fallback}`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* ifEmpty.fn("", ["no-title"]); // => "no-title"
|
|
13
|
+
* ifEmpty.fn("hello", ["unused"]); // => "hello"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
1
16
|
export const ifEmpty = {
|
|
2
17
|
name: "ifEmpty",
|
|
3
18
|
fn: (value, [fallback]) => {
|