new-branch 0.1.1 → 0.2.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 CHANGED
@@ -119,13 +119,11 @@ Example AST representation:
119
119
 
120
120
  ## 6. Built-in Variables
121
121
 
122
- Variable Description
123
-
124
- ---
125
-
126
- type Branch type (feat, fix, etc.)
127
- title Human-readable task title
128
- id Task identifier (e.g., STK-123)
122
+ | Variable | Description |
123
+ | -------- | ------------------------------- |
124
+ | type | Branch type (feat, fix, etc.) |
125
+ | title | Human-readable task title |
126
+ | id | Task identifier (e.g., STK-123) |
129
127
 
130
128
  ---
131
129
 
@@ -135,28 +133,29 @@ All transforms must be pure functions.
135
133
 
136
134
  ### 7.1 String Transforms
137
135
 
138
- | Transform \| Description \|
139
-
140
- \|------------\|-------------\| slugify \| Converts to URL-safe slug \|
141
- \| lowercase \| Converts to lowercase \| \| uppercase \| Converts to
142
- uppercase \| \| trim \| Trims whitespace \| \| titlecase \| Capitalizes
143
- words \|
136
+ | Transform | Description |
137
+ | --------- | ------------------------- |
138
+ | slugify | Converts to URL-safe slug |
139
+ | lowercase | Converts to lowercase |
140
+ | uppercase | Converts to uppercase |
141
+ | trim | Trims whitespace |
142
+ | titlecase | Capitalizes words |
144
143
 
145
144
  ### 7.2 Argument-based Transforms
146
145
 
147
- | Transform \| Description \| Example \|
148
-
149
- \|------------\|-------------\|---------\| max \| Truncates string to
150
- max length \| max:25 \| \| pad \| Pads string to length \| pad:10 \|
146
+ | Transform | Description | Example |
147
+ | --------- | ------------------------------ | ------- |
148
+ | max | Truncates string to max length | max:25 |
149
+ | pad | Pads string to length | pad:10 |
151
150
 
152
151
  ### 7.3 Validation Transforms
153
152
 
154
- Validation transforms do not modify value but throw errors if invalid.
153
+ Validation transforms do not modify a value but throw errors if invalid.
155
154
 
156
- | Transform \| Description \|
157
-
158
- \|------------\|-------------\| required \| Ensures value is not empty
159
- \| \| match \| Validates via regex \|
155
+ | Transform | Description |
156
+ | --------- | -------------------------- |
157
+ | required | Ensures value is not empty |
158
+ | match | Validates value via regex |
160
159
 
161
160
  ---
162
161
 
@@ -237,12 +236,10 @@ Validation must occur immediately after input.
237
236
 
238
237
  ## 12. Optional Flags
239
238
 
240
- Flag Description
241
-
242
- ---
243
-
244
- --create Creates branch using `git switch -c`
245
- --print Prints branch name only (default behavior)
239
+ | Flag | Description |
240
+ | ---------- | ------------------------------------------ |
241
+ | `--create` | Creates branch using `git switch -c` |
242
+ | `--print` | Prints branch name only (default behavior) |
246
243
 
247
244
  ---
248
245
 
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { parsePattern } from "./pattern/parsePattern.js";
4
4
  import { defaultTransforms } from "./pattern/transforms/index.js";
5
5
  import { renderPattern } from "./pattern/transforms/renderPattern.js";
6
6
  import { resolveMissingValues } from "./runtime/resolveMissingValues.js";
7
+ import { loadProjectConfig } from "./config/loadProjectConfig.js";
7
8
  import { sanitizeGitRef } from "./git/sanitizeGitRef.js";
8
9
  import { validateBranchName } from "./git/validateBranchName.js";
9
10
  import { createBranch } from "./git/createBranch.js";
@@ -47,15 +48,22 @@ function toInitialValues(args) {
47
48
  }
48
49
  async function run() {
49
50
  // Step 0: args/options
51
+ // Note: CAC prints help, but depending on our parseArgs wrapper we might not
52
+ // expose `help` in `args.options`. We still want to exit early and never
53
+ // require a pattern when the user just asked for help.
54
+ const argv = process.argv.slice(2);
55
+ const wantsHelp = argv.includes("--help") || argv.includes("-h");
50
56
  const args = parseArgs(process.argv);
51
- if (args.options.help) {
57
+ if (wantsHelp) {
52
58
  return;
53
59
  }
54
60
  const quiet = args.options.quiet === true;
55
61
  const create = args.options.create === true;
56
62
  const prompt = args.options.prompt !== false;
57
63
  // Pipeline: pattern -> AST -> resolve values -> render -> sanitize -> validate -> (optional) git -> output
58
- const patternRes = requirePattern(args.options.pattern);
64
+ const projectConfig = await loadProjectConfig();
65
+ const resolvedPattern = args.options.pattern ?? projectConfig.pattern;
66
+ const patternRes = requirePattern(resolvedPattern);
59
67
  if (!isOk(patternRes))
60
68
  fail("Invalid CLI arguments.", patternRes.error);
61
69
  const astRes = safe(() => parsePattern(patternRes.value));
@@ -0,0 +1,19 @@
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
+ }
@@ -0,0 +1,34 @@
1
+ import { vi, describe, it, expect, beforeEach } from "vitest";
2
+ vi.mock("node:fs/promises", () => ({
3
+ readFile: vi.fn(),
4
+ }));
5
+ import { readFile } from "node:fs/promises";
6
+ describe("loadProjectConfig", () => {
7
+ beforeEach(() => {
8
+ vi.resetAllMocks();
9
+ });
10
+ it("returns pattern when package.json contains new-branch.pattern as string", async () => {
11
+ readFile.mockResolvedValueOnce(JSON.stringify({ "new-branch": { pattern: "{type}/{title}-{id}" } }));
12
+ const { loadProjectConfig } = await import("./loadProjectConfig.js");
13
+ const cfg = await loadProjectConfig();
14
+ expect(cfg).toEqual({ pattern: "{type}/{title}-{id}" });
15
+ });
16
+ it("returns empty object when package.json has no new-branch key", async () => {
17
+ readFile.mockResolvedValueOnce(JSON.stringify({ name: "pkg" }));
18
+ const { loadProjectConfig } = await import("./loadProjectConfig.js");
19
+ const cfg = await loadProjectConfig();
20
+ expect(cfg).toEqual({});
21
+ });
22
+ it("ignores non-string pattern values", async () => {
23
+ readFile.mockResolvedValueOnce(JSON.stringify({ "new-branch": { pattern: 123 } }));
24
+ const { loadProjectConfig } = await import("./loadProjectConfig.js");
25
+ const cfg = await loadProjectConfig();
26
+ expect(cfg).toEqual({});
27
+ });
28
+ it("returns empty object if reading package.json throws", async () => {
29
+ readFile.mockRejectedValueOnce(new Error("enoent"));
30
+ const { loadProjectConfig } = await import("./loadProjectConfig.js");
31
+ const cfg = await loadProjectConfig();
32
+ expect(cfg).toEqual({});
33
+ });
34
+ });
@@ -0,0 +1,11 @@
1
+ export const TYPE_CHOICES = [
2
+ { name: "Feature", value: "feat" },
3
+ { name: "Fix", value: "fix" },
4
+ { name: "Documentation", value: "docs" },
5
+ { name: "Chore", value: "chore" },
6
+ { name: "Refactor", value: "refactor" },
7
+ { name: "Test", value: "test" },
8
+ { name: "Performance", value: "perf" },
9
+ { name: "Build", value: "build" },
10
+ { name: "CI", value: "ci" },
11
+ ];
@@ -1,19 +1,30 @@
1
- import { input } from "@inquirer/prompts";
1
+ import { input, select } from "@inquirer/prompts";
2
+ import { TYPE_CHOICES } from "../runtime/enums.js";
2
3
  /**
3
4
  * Resolves missing variable values required by the pattern.
4
5
  *
5
6
  * Rule (v1):
6
7
  * - Any variable used in the pattern is considered required.
8
+ * - The "type" variable is resolved using a select menu with predefined choices.
7
9
  */
8
10
  export async function resolveMissingValues(parsed, initialValues, opts) {
9
11
  const requiredVars = parsed.variablesUsed;
10
12
  const values = { ...initialValues };
11
13
  for (const name of requiredVars) {
12
- if (values[name])
14
+ const current = values[name];
15
+ if (current && current.trim() !== "")
13
16
  continue;
14
17
  if (!opts.prompt) {
15
18
  throw new Error(`Missing required value: "${name}"`);
16
19
  }
20
+ if (name === "type") {
21
+ const selected = await select({
22
+ message: "Select branch type:",
23
+ choices: TYPE_CHOICES,
24
+ });
25
+ values[name] = selected;
26
+ continue;
27
+ }
17
28
  const answer = await input({
18
29
  message: `Enter ${name}:`,
19
30
  validate: (v) => (v.trim() ? true : `${name} cannot be empty`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "new-branch",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Generate and create standardized git branch names from a pattern.",
5
5
  "keywords": [
6
6
  "git",
@@ -18,8 +18,8 @@
18
18
  "start": "node dist/cli.js",
19
19
  "typecheck": "tsc -p tsconfig.json --noEmit",
20
20
  "prepack": "pnpm build && pnpm test:run",
21
- "test": "vitest",
22
- "test:run": "vitest run",
21
+ "test": "vitest --dir src --exclude **/dist/**",
22
+ "test:run": "vitest run --dir src --exclude **/dist/**",
23
23
  "format": "prettier . --write",
24
24
  "format:check": "prettier . --check",
25
25
  "lint": "eslint src --ext .ts"
@@ -40,6 +40,9 @@
40
40
  "bugs": {
41
41
  "url": "https://github.com/teles/new-branch/issues"
42
42
  },
43
+ "new-branch": {
44
+ "pattern": "{type}/{title}-{id}"
45
+ },
43
46
  "homepage": "https://github.com/teles/new-branch#readme",
44
47
  "packageManager": "pnpm@10.22.0",
45
48
  "devDependencies": {