@pc360/chlog 0.1.11 → 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/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
10
10
 
11
11
  - No changes yet.
12
12
 
13
+ ## [0.2.0] - 2026-02-16
14
+
15
+ ### Added
16
+
17
+ - Added graceful interactive cancel handling: pressing `Ctrl + C` now exits cleanly with `Operation cancelled by user.`
18
+ - Added unit tests for prompt cancellation detection.
19
+
20
+ ### Changed
21
+
22
+ - Updated README CLI rules to document interactive cancellation behavior and the exact cancellation message.
23
+ - Simplified unit tests by removing redundant parser test cases already covered by CLI integration tests.
24
+
13
25
  ## [0.1.11] - 2026-02-15
14
26
 
15
27
  ### Added
package/README.md CHANGED
@@ -36,7 +36,7 @@ npm i -g @pc360/chlog
36
36
  Alternative (without global install):
37
37
 
38
38
  ```bash
39
- npx @pc360/chlog --type <added|changed|fixed|removed> --scope <scope> --message "<description>"
39
+ npx @pc360/chlog
40
40
  ```
41
41
 
42
42
  ---
@@ -46,19 +46,13 @@ npx @pc360/chlog --type <added|changed|fixed|removed> --scope <scope> --message
46
46
  For first use, always run a dry run to preview output without creating a file:
47
47
 
48
48
  ```bash
49
- chlog --type added --scope JES-99 --message "Test entry" --dry-run
49
+ chlog --dry-run
50
50
  ```
51
51
 
52
- Short form:
52
+ Then run interactively:
53
53
 
54
54
  ```bash
55
- chlog -t added -s JES-99 -m "Test entry" -d
56
- ```
57
-
58
- If the preview is correct, run the actual command:
59
-
60
- ```bash
61
- chlog --type added --scope JES-99 --message "Test entry"
55
+ chlog
62
56
  ```
63
57
 
64
58
  ---
@@ -194,56 +188,14 @@ chlog --version
194
188
 
195
189
  ---
196
190
 
197
- # Usage
198
-
199
- ```bash
200
- chlog --type <added|changed|fixed|removed> \
201
- --scope <scope> \
202
- --message "<description>" \
203
- [--frontend]
204
- ```
205
-
206
- Short form:
207
-
208
- ```bash
209
- chlog -t <added|changed|fixed|removed> -s <scope> -m "<description>" [-f]
210
- ```
211
-
212
- ---
213
-
214
191
  # Parameters
215
192
 
216
- | Flag | Required | Description |
217
- | ---------------- | -------- | --------------------------------------------------- |
218
- | `-t, --type` | Yes | Change type: `added`, `changed`, `fixed`, `removed` |
219
- | `-s, --scope` | Yes | Jira ticket or module name |
220
- | `-m, --message` | Yes | Description of the change |
221
- | `-f, --frontend` | No | Uses `[Front-End]` instead of `[Back-End]` |
222
- | `-d, --dry-run` | No | Preview file creation without writing |
223
- | `-h, --help` | No | Show help |
224
- | `-v, --version` | No | Show CLI version |
225
-
226
- ---
227
-
228
- # Examples
229
-
230
- ### Back-End (default)
231
-
232
- ```bash
233
- chlog --type added --scope JES-33 --message "Add daily consolidated JE job"
234
- ```
235
-
236
- ### Front-End
237
-
238
- ```bash
239
- chlog --type fixed --scope JES-45 --frontend --message "Fix table pagination issue"
240
- ```
241
-
242
- ### Module Scope
243
-
244
- ```bash
245
- chlog --type changed --scope "Credit Validation" --message "Improve credit limit computation"
246
- ```
193
+ | Flag | Required | Description |
194
+ | --------------- | -------- | -------------------------------------------------------- |
195
+ | `-d, --dry-run` | No | Preview file creation without writing |
196
+ | `--tz` | No | Timezone for filename timestamp (default: `Asia/Manila`) |
197
+ | `-h, --help` | No | Show help |
198
+ | `-v, --version` | No | Show CLI version |
247
199
 
248
200
  ---
249
201
 
@@ -270,9 +222,12 @@ Timezone used: `Asia/Manila`
270
222
 
271
223
  # CLI Rules
272
224
 
273
- - `--type` is required
274
- - `--scope` is required
275
- - `--message` is required
225
+ - Type, scope, message, and layer are collected interactively
226
+ - Type is selected inline/horizontally (`added / changed / fixed / removed`)
227
+ - Layer uses a `Is frontend?` toggle (`No` by default = `Back-End`)
228
+ - After message input in interactive mode, a toggle asks `Display preview?` (`Yes/No`)
229
+ - If preview is shown, a second toggle asks `Do you want to create changelog?` (`Yes/No`)
230
+ - Pressing `Ctrl + C` during interactive prompts cancels the operation and prints `Operation cancelled by user.`
276
231
  - Existing files are not overwritten
277
232
  - Layer tag is auto-added (`[Back-End]` or `[Front-End]`)
278
233
 
package/bin/chlog.js CHANGED
@@ -17,6 +17,12 @@ const {
17
17
  renderSuccess,
18
18
  renderError,
19
19
  } = require("../lib/chlog-ui");
20
+ const {
21
+ promptForMissingArgs,
22
+ promptYesNo,
23
+ isInteractiveTerminal,
24
+ isPromptCancelled,
25
+ } = require("../lib/chlog-prompts");
20
26
 
21
27
  function getHelpText() {
22
28
  return `
@@ -29,14 +35,10 @@ Each entry is formatted as:
29
35
  [<scope>] [Back-End|Front-End] <message>
30
36
 
31
37
  Usage:
32
- chlog --type <added|changed|fixed|removed> --scope <ticket> --message "<text>" [--frontend]
33
- chlog -t <added|changed|fixed|removed> -s <ticket> -m "<text>" [-f]
38
+ chlog
34
39
 
35
40
  Options:
36
- -t, --type <t> Entry type (required)
37
- -s, --scope <ticket> Jira ticket or scope (required)
38
- -m, --message "<msg>" Entry message (required)
39
- -f, --frontend Use [Front-End] instead of [Back-End]
41
+ (type, scope, message, and layer are collected interactively)
40
42
  --tz "<iana>" Timezone (default: Asia/Manila)
41
43
  -d, --dry-run Preview only
42
44
  -h, --help Show help
@@ -46,7 +48,7 @@ Options:
46
48
 
47
49
  async function main() {
48
50
  try {
49
- const args = parseArgs(process.argv);
51
+ let args = parseArgs(process.argv);
50
52
 
51
53
  if (args.help) {
52
54
  await renderHelp(getHelpText());
@@ -58,6 +60,10 @@ async function main() {
58
60
  process.exit(0);
59
61
  }
60
62
 
63
+ if (isInteractiveTerminal()) {
64
+ args = await promptForMissingArgs(args);
65
+ }
66
+
61
67
  validateArgs(args);
62
68
 
63
69
  const timestamp = generateTimestamp(new Date(), args.tz);
@@ -82,6 +88,17 @@ async function main() {
82
88
  process.exit(0);
83
89
  }
84
90
 
91
+ if (isInteractiveTerminal() && !args.dryRunSet) {
92
+ const shouldPreview = await promptYesNo("Display preview?", false);
93
+ if (shouldPreview) {
94
+ await renderDryRun({ entryPath, content });
95
+ const shouldExecute = await promptYesNo("Do you want to create changelog?", true);
96
+ if (!shouldExecute) {
97
+ process.exit(0);
98
+ }
99
+ }
100
+ }
101
+
85
102
  await fs.mkdir(entryDir, { recursive: true });
86
103
 
87
104
  await fs.writeFile(entryPath, content, {
@@ -91,6 +108,11 @@ async function main() {
91
108
 
92
109
  await renderSuccess({ entryPath });
93
110
  } catch (err) {
111
+ if (isPromptCancelled(err)) {
112
+ process.stdout.write("Operation cancelled by user.\n");
113
+ process.exit(0);
114
+ }
115
+
94
116
  await renderError(err);
95
117
  process.exit(1);
96
118
  }
package/lib/chlog-lib.js CHANGED
@@ -9,7 +9,9 @@ function parseArgs(argv) {
9
9
  message: null,
10
10
  tz: "Asia/Manila",
11
11
  frontend: false,
12
+ layerSet: false,
12
13
  dryRun: false,
14
+ dryRunSet: false,
13
15
  help: false,
14
16
  version: false,
15
17
  };
@@ -29,26 +31,7 @@ function parseArgs(argv) {
29
31
 
30
32
  if (arg === "--dry-run" || arg === "-d") {
31
33
  args.dryRun = true;
32
- continue;
33
- }
34
-
35
- if (arg === "--frontend" || arg === "-f") {
36
- args.frontend = true;
37
- continue;
38
- }
39
-
40
- if (arg === "--type" || arg === "-t") {
41
- args.type = argv[++i];
42
- continue;
43
- }
44
-
45
- if (arg === "--scope" || arg === "-s") {
46
- args.scope = argv[++i];
47
- continue;
48
- }
49
-
50
- if (arg === "--message" || arg === "-m") {
51
- args.message = argv[++i];
34
+ args.dryRunSet = true;
52
35
  continue;
53
36
  }
54
37
 
@@ -67,13 +50,19 @@ function validateArgs(args) {
67
50
  if (!args.type || !VALID_TYPES.has(args.type)) {
68
51
  throw new Error(`--type must be one of: added, changed, fixed, removed`);
69
52
  }
70
- if (!args.scope) {
53
+ const normalizedScope = String(args.scope ?? "").trim();
54
+ const normalizedMessage = String(args.message ?? "").trim();
55
+
56
+ if (!normalizedScope) {
71
57
  throw new Error(`--scope is required (e.g. JES-33)`);
72
58
  }
73
- if (!args.message) {
59
+ if (!normalizedMessage) {
74
60
  throw new Error(`--message is required`);
75
61
  }
76
62
 
63
+ args.scope = normalizedScope;
64
+ args.message = normalizedMessage;
65
+
77
66
  // Validate timezone (throws RangeError if invalid)
78
67
  new Intl.DateTimeFormat("en-US", { timeZone: args.tz }).format(new Date());
79
68
 
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+
3
+ const { VALID_TYPES } = require("./chlog-lib");
4
+
5
+ class InlineChoicePrompt {
6
+ constructor({ message, choices, initial = 0 }) {
7
+ const Prompt = require("enquirer/lib/prompt");
8
+
9
+ this.Prompt = class extends Prompt {
10
+ constructor(options) {
11
+ super(options);
12
+ this.cursorHide();
13
+ this.choices = options.choices.map((choice) =>
14
+ typeof choice === "string"
15
+ ? { name: choice, message: choice }
16
+ : { name: choice.name, message: choice.message ?? choice.name },
17
+ );
18
+ this.index = Math.max(
19
+ 0,
20
+ Math.min(options.initial ?? 0, this.choices.length - 1),
21
+ );
22
+ this.value = this.choices[this.index]?.name;
23
+ }
24
+
25
+ left() {
26
+ if (this.index <= 0) {
27
+ this.alert();
28
+ return;
29
+ }
30
+ this.index -= 1;
31
+ this.value = this.choices[this.index].name;
32
+ this.render();
33
+ }
34
+
35
+ right() {
36
+ if (this.index >= this.choices.length - 1) {
37
+ this.alert();
38
+ return;
39
+ }
40
+ this.index += 1;
41
+ this.value = this.choices[this.index].name;
42
+ this.render();
43
+ }
44
+
45
+ up() {
46
+ this.left();
47
+ }
48
+
49
+ down() {
50
+ this.right();
51
+ }
52
+
53
+ number(input) {
54
+ const nextIndex = Number(input) - 1;
55
+ if (
56
+ Number.isNaN(nextIndex) ||
57
+ nextIndex < 0 ||
58
+ nextIndex >= this.choices.length
59
+ ) {
60
+ this.alert();
61
+ return;
62
+ }
63
+ this.index = nextIndex;
64
+ this.value = this.choices[this.index].name;
65
+ this.render();
66
+ }
67
+
68
+ format() {
69
+ return this.choices
70
+ .map((choice, idx) => {
71
+ if (idx === this.index) {
72
+ return this.styles.primary.underline(choice.message);
73
+ }
74
+ return this.styles.muted(choice.message);
75
+ })
76
+ .join(this.styles.muted(" / "));
77
+ }
78
+
79
+ async render() {
80
+ const { size } = this.state;
81
+
82
+ const header = await this.header();
83
+ const prefix = await this.prefix();
84
+ const separator = await this.separator();
85
+ const message = await this.message();
86
+ const output = this.format();
87
+ const help =
88
+ (await this.error()) ||
89
+ this.styles.muted("(left/right or 1-4, enter)");
90
+ const footer = await this.footer();
91
+
92
+ let prompt = [prefix, message, separator, output].join(" ");
93
+ this.state.prompt = prompt;
94
+ if (help && !prompt.includes(help)) {
95
+ prompt += " " + help;
96
+ }
97
+
98
+ this.clear(size);
99
+ this.write([header, prompt, footer].filter(Boolean).join("\n"));
100
+ this.write(this.margin[2]);
101
+ this.restore();
102
+ }
103
+ };
104
+
105
+ this.options = { name: "inlineChoice", message, choices, initial };
106
+ }
107
+
108
+ async run() {
109
+ const prompt = new this.Prompt(this.options);
110
+ return prompt.run();
111
+ }
112
+ }
113
+
114
+ async function promptForMissingArgs(args) {
115
+ const { Toggle, Input } = require("enquirer");
116
+ const nextArgs = { ...args };
117
+
118
+ if (!nextArgs.type) {
119
+ const typePrompt = new InlineChoicePrompt({
120
+ message: "Select changelog type:",
121
+ choices: Array.from(VALID_TYPES),
122
+ initial: 0,
123
+ });
124
+ nextArgs.type = await typePrompt.run();
125
+ }
126
+
127
+ if (!nextArgs.layerSet) {
128
+ const layerPrompt = new Toggle({
129
+ name: "frontend",
130
+ message: "Is frontend?",
131
+ enabled: "Yes",
132
+ disabled: "No",
133
+ initial: false,
134
+ });
135
+ nextArgs.frontend = Boolean(await layerPrompt.run());
136
+ nextArgs.layerSet = true;
137
+ }
138
+
139
+ if (!nextArgs.scope) {
140
+ const scopePrompt = new Input({
141
+ name: "scope",
142
+ message: "Enter scope/ticket (e.g. JES-33)",
143
+ validate: (value) =>
144
+ String(value ?? "").trim() ? true : "Scope is required",
145
+ });
146
+ nextArgs.scope = String(await scopePrompt.run()).trim();
147
+ }
148
+
149
+ if (!nextArgs.message) {
150
+ const messagePrompt = new Input({
151
+ name: "message",
152
+ message: "Enter changelog message",
153
+ validate: (value) =>
154
+ String(value ?? "").trim() ? true : "Message is required",
155
+ });
156
+ nextArgs.message = String(await messagePrompt.run()).trim();
157
+ }
158
+
159
+ return nextArgs;
160
+ }
161
+
162
+ async function promptYesNo(message, initial = false) {
163
+ const { Toggle } = require("enquirer");
164
+ const prompt = new Toggle({
165
+ name: "confirm",
166
+ message,
167
+ enabled: "Yes",
168
+ disabled: "No",
169
+ initial,
170
+ });
171
+
172
+ return Boolean(await prompt.run());
173
+ }
174
+
175
+ function isInteractiveTerminal() {
176
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
177
+ }
178
+
179
+ function isPromptCancelled(error) {
180
+ if (error == null || error === "") {
181
+ return true;
182
+ }
183
+
184
+ if (typeof error === "string") {
185
+ return /cancel(?:led|ed)?|abort(?:ed)?|interrupt(?:ed)?/i.test(error);
186
+ }
187
+
188
+ if (error instanceof Error) {
189
+ return /cancel(?:led|ed)?|abort(?:ed)?|interrupt(?:ed)?/i.test(error.message);
190
+ }
191
+
192
+ return false;
193
+ }
194
+
195
+ module.exports = {
196
+ promptForMissingArgs,
197
+ promptYesNo,
198
+ isInteractiveTerminal,
199
+ isPromptCancelled,
200
+ };
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@pc360/chlog",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "PC360 Changelog CLI Tool",
5
- "author": "aalmazan@pcdsi.ph",
5
+ "keywords": [
6
+ "changelog",
7
+ "cli"
8
+ ],
9
+ "author": "almerleoalmazan@gmail.com",
6
10
  "bin": {
7
11
  "chlog": "bin/chlog.js"
8
12
  },
@@ -21,6 +25,7 @@
21
25
  },
22
26
  "dependencies": {
23
27
  "@pc360/chlog": "^0.1.7",
28
+ "enquirer": "^2.4.1",
24
29
  "ink": "^4.4.1",
25
30
  "react": "^18.3.1"
26
31
  },