new-branch 0.6.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/dist/cli.js +59 -5
- package/dist/config/loadConfig.js +11 -7
- package/dist/config/sources/git.loader.js +19 -2
- package/dist/config/validate.js +15 -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/gitConfig.js +28 -0
- package/dist/parseArgs.js +8 -0
- 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/package.json +1 -1
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;
|
|
@@ -74,17 +77,52 @@ export async function run() {
|
|
|
74
77
|
if (wantsHelp) {
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
80
|
+
// --- Didactic mode: --list-transforms ---
|
|
81
|
+
if (args.options.listTransforms) {
|
|
82
|
+
console.log(listTransforms(allTransforms));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
77
85
|
const quiet = args.options.quiet === true;
|
|
78
86
|
const create = args.options.create === true;
|
|
79
87
|
const prompt = args.options.prompt !== false;
|
|
88
|
+
const isExplain = args.options.explain === true;
|
|
80
89
|
// Pipeline: pattern -> AST -> resolve values -> render -> sanitize -> validate -> (optional) git -> output
|
|
81
|
-
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
|
+
}
|
|
82
96
|
// Git config (respects local -> global precedence automatically)
|
|
83
97
|
let gitPattern;
|
|
84
|
-
if (!args.options.pattern && !projectConfig.pattern) {
|
|
98
|
+
if (!args.options.pattern && !args.options.use && !projectConfig.pattern) {
|
|
85
99
|
gitPattern = await getGitConfig("new-branch.pattern");
|
|
86
100
|
}
|
|
87
|
-
|
|
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)";
|
|
88
126
|
const patternRes = requirePattern(resolvedPattern);
|
|
89
127
|
if (!isOk(patternRes))
|
|
90
128
|
fail("Invalid CLI arguments.", patternRes.error);
|
|
@@ -135,6 +173,22 @@ export async function run() {
|
|
|
135
173
|
if (!isOk(renderedRes))
|
|
136
174
|
fail("Failed to render branch name.", renderedRes.error);
|
|
137
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
|
+
}
|
|
138
192
|
const validateRes = await safeAsync(() => validateBranchName(sanitized));
|
|
139
193
|
if (!isOk(validateRes))
|
|
140
194
|
fail("Branch name is not valid for git.", validateRes.error);
|
|
@@ -15,17 +15,21 @@ import { gitLoader } from "./sources/git.loader.js";
|
|
|
15
15
|
* No merging is performed.
|
|
16
16
|
*/
|
|
17
17
|
export async function loadConfig() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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() {
|
|
21
25
|
const rcRes = await rcLoader.load();
|
|
22
26
|
if (rcRes.found && rcRes.config && Object.keys(rcRes.config).length > 0)
|
|
23
|
-
return rcRes.config;
|
|
27
|
+
return { config: rcRes.config, source: ".newbranchrc.json" };
|
|
24
28
|
const pkgRes = await packageJsonLoader.load();
|
|
25
29
|
if (pkgRes.found && pkgRes.config && Object.keys(pkgRes.config).length > 0)
|
|
26
|
-
return pkgRes.config;
|
|
30
|
+
return { config: pkgRes.config, source: "package.json" };
|
|
27
31
|
const gitRes = await gitLoader.load();
|
|
28
32
|
if (gitRes.found)
|
|
29
|
-
return gitRes.config ?? {};
|
|
30
|
-
return {};
|
|
33
|
+
return { config: gitRes.config ?? {}, source: "git config" };
|
|
34
|
+
return { config: {}, source: "(none)" };
|
|
31
35
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getGitConfig } from "../../git/gitConfig.js";
|
|
1
|
+
import { getGitConfig, getGitConfigRegexp } from "../../git/gitConfig.js";
|
|
2
2
|
import { validateProjectConfigSource, validateProjectConfigFinal } from "../validate.js";
|
|
3
3
|
function parseGitTypes(raw) {
|
|
4
4
|
return raw
|
|
@@ -16,13 +16,27 @@ function parseGitTypes(raw) {
|
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
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
|
+
}
|
|
19
32
|
export const gitLoader = {
|
|
20
33
|
source: "git",
|
|
21
34
|
async load() {
|
|
22
35
|
const pattern = await getGitConfig("new-branch.pattern");
|
|
23
36
|
const defaultType = await getGitConfig("new-branch.defaultType");
|
|
24
37
|
const typesRaw = await getGitConfig("new-branch.types");
|
|
25
|
-
|
|
38
|
+
const patternsEntries = await getGitConfigRegexp("^new-branch\\.patterns\\.");
|
|
39
|
+
if (!pattern && !defaultType && !typesRaw && patternsEntries.length === 0) {
|
|
26
40
|
return { found: false, source: "git", config: undefined };
|
|
27
41
|
}
|
|
28
42
|
const cfg = {};
|
|
@@ -32,6 +46,9 @@ export const gitLoader = {
|
|
|
32
46
|
cfg.defaultType = defaultType;
|
|
33
47
|
if (typesRaw)
|
|
34
48
|
cfg.types = parseGitTypes(typesRaw);
|
|
49
|
+
const patterns = parseGitPatterns(patternsEntries);
|
|
50
|
+
if (patterns)
|
|
51
|
+
cfg.patterns = patterns;
|
|
35
52
|
const sourceValidated = validateProjectConfigSource(cfg, "git config");
|
|
36
53
|
const finalValidated = validateProjectConfigFinal(sourceValidated, "git config");
|
|
37
54
|
return { found: true, source: "git", config: finalValidated };
|
package/dist/config/validate.js
CHANGED
|
@@ -48,6 +48,21 @@ export function validateProjectConfigSource(raw, source) {
|
|
|
48
48
|
invariant(isString(obj.defaultType), source, "defaultType must be a string");
|
|
49
49
|
cfg.defaultType = trimOrUndefined(obj.defaultType);
|
|
50
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
|
+
}
|
|
51
66
|
if ("types" in obj) {
|
|
52
67
|
const typesVal = obj.types;
|
|
53
68
|
invariant(Array.isArray(typesVal), source, "types must be an array");
|
|
@@ -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/gitConfig.js
CHANGED
|
@@ -9,3 +9,31 @@ export async function getGitConfig(key) {
|
|
|
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,12 +10,16 @@ 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);
|
|
@@ -23,6 +27,7 @@ export function parseArgs(argv = process.argv) {
|
|
|
23
27
|
const options = {
|
|
24
28
|
// Accept numeric or string-like values and coerce to string when present.
|
|
25
29
|
pattern: opts.pattern !== undefined ? String(opts.pattern) : undefined,
|
|
30
|
+
use: opts.use !== undefined ? String(opts.use) : undefined,
|
|
26
31
|
id: opts.id !== undefined ? String(opts.id) : undefined,
|
|
27
32
|
title: opts.title !== undefined ? String(opts.title) : undefined,
|
|
28
33
|
type: opts.type !== undefined ? String(opts.type) : undefined,
|
|
@@ -32,6 +37,9 @@ export function parseArgs(argv = process.argv) {
|
|
|
32
37
|
prompt: typeof opts.prompt === "boolean" ? opts.prompt : undefined,
|
|
33
38
|
quiet: typeof opts.quiet === "boolean" ? opts.quiet : undefined,
|
|
34
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,
|
|
35
43
|
};
|
|
36
44
|
return {
|
|
37
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
|
+
};
|