@snelusha/noto 1.0.0-beta.3 → 1.0.0-beta.5

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.
Files changed (2) hide show
  1. package/dist/index.js +229 -61
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import * as p5 from "@clack/prompts";
3
- import color5 from "picocolors";
2
+ import * as p6 from "@clack/prompts";
3
+ import color6 from "picocolors";
4
4
 
5
5
  // src/utils/parser.ts
6
6
  import arg from "arg";
@@ -47,7 +47,8 @@ var StorageSchema = z2.object({
47
47
  llm: z2.object({
48
48
  apiKey: z2.string().optional(),
49
49
  model: AvailableModelsSchema.optional()
50
- }).optional()
50
+ }).optional(),
51
+ lastGeneratedMessage: z2.string().optional()
51
52
  });
52
53
  var StorageManager = class {
53
54
  static storagePath = resolve(
@@ -94,19 +95,25 @@ var StorageManager = class {
94
95
  }
95
96
  };
96
97
 
98
+ // src/utils/process.ts
99
+ var exit = async (code) => {
100
+ await new Promise((resolve2) => setTimeout(resolve2, 1));
101
+ console.log();
102
+ process.exit(code);
103
+ };
104
+
97
105
  // src/middleware/auth.ts
98
- var withAuth = (fn) => {
99
- return async (options) => {
106
+ var withAuth = (fn, options = {}) => {
107
+ return async (opts) => {
100
108
  const storage = await StorageManager.get();
101
- if (!storage.llm?.apiKey) {
109
+ if (!storage.llm?.apiKey && !options.silent) {
102
110
  p.log.error(
103
111
  dedent`${color.red("noto api key is missing.")}
104
112
  ${color.dim(`run ${color.cyan("`noto config key`")} to set it up.`)}`
105
113
  );
106
- console.log();
107
- process.exit(1);
114
+ return await exit(1);
108
115
  }
109
- return fn(options);
116
+ return fn(opts);
110
117
  };
111
118
  };
112
119
 
@@ -140,28 +147,27 @@ var commit = async (message) => {
140
147
  };
141
148
 
142
149
  // src/middleware/git.ts
143
- var withRepository = (fn) => {
144
- return async (options) => {
150
+ var withRepository = (fn, options = {}) => {
151
+ return async (opts) => {
145
152
  const isRepo = await isGitRepository();
146
- if (!isRepo) {
153
+ if (!isRepo && !options.silent) {
147
154
  p2.log.error(
148
155
  dedent2`${color2.red("no git repository found in cwd.")}
149
156
  ${color2.dim(`run ${color2.cyan("`git init`")} to initialize a new repository.`)}`
150
157
  );
151
- console.log();
152
- process.exit(1);
158
+ return await exit(1);
153
159
  }
160
+ opts.isRepo = isRepo;
154
161
  const diff = await getStagedDiff();
155
- if (!diff) {
162
+ if (!diff && !options.silent) {
156
163
  p2.log.error(
157
164
  dedent2`${color2.red("no staged changes found.")}
158
165
  ${color2.dim(`run ${color2.cyan("`git add <file>`")} or ${color2.cyan("`git add .`")} to stage changes.`)}`
159
166
  );
160
- console.log();
161
- process.exit(1);
167
+ return await exit(1);
162
168
  }
163
- options.diff = diff;
164
- return fn(options);
169
+ opts.diff = diff;
170
+ return fn(opts);
165
171
  };
166
172
  };
167
173
 
@@ -214,7 +220,7 @@ var getModel = async () => {
214
220
  };
215
221
 
216
222
  // src/ai/index.ts
217
- var generateCommitMessage = async (diff) => {
223
+ var generateCommitMessage = async (diff, type) => {
218
224
  const model2 = await getModel();
219
225
  const { object } = await generateObject({
220
226
  model: model2,
@@ -236,7 +242,11 @@ var generateCommitMessage = async (diff) => {
236
242
  6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.
237
243
  7. Prioritize clarity and focus on the most impactful changes for the commit.
238
244
 
239
- You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`
245
+ You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.
246
+
247
+ ${type ? `if a type is provided (e.g., feat, fix), include it in the commit message in the format "type: message"
248
+
249
+ type: ${type}` : ""}`
240
250
  },
241
251
  {
242
252
  role: "user",
@@ -254,6 +264,12 @@ var command = {
254
264
  description: "generate commit message",
255
265
  usage: "noto [options]",
256
266
  options: [
267
+ {
268
+ type: Boolean,
269
+ flag: "--type",
270
+ alias: "-t",
271
+ description: "generate commit message based on type"
272
+ },
257
273
  {
258
274
  type: Boolean,
259
275
  flag: "--copy",
@@ -265,6 +281,12 @@ var command = {
265
281
  flag: "--apply",
266
282
  alias: "-a",
267
283
  description: "commit the generated message directly"
284
+ },
285
+ {
286
+ type: Boolean,
287
+ flag: "--edit",
288
+ alias: "-e",
289
+ description: "edit the generated commit message"
268
290
  }
269
291
  ],
270
292
  execute: withAuth(
@@ -272,9 +294,67 @@ var command = {
272
294
  const spin = p3.spinner();
273
295
  try {
274
296
  const { diff } = options;
297
+ const isEditMode = options["--edit"];
298
+ if (options["--type"]) {
299
+ const type = await p3.select({
300
+ message: "select the type of commit message",
301
+ options: [
302
+ {
303
+ label: "chore",
304
+ value: "chore"
305
+ },
306
+ {
307
+ label: "feat",
308
+ value: "feat"
309
+ },
310
+ {
311
+ label: "fix",
312
+ value: "fix"
313
+ },
314
+ {
315
+ label: "docs",
316
+ value: "docs"
317
+ },
318
+ {
319
+ label: "refactor",
320
+ value: "refactor"
321
+ },
322
+ {
323
+ label: "perf",
324
+ value: "perf"
325
+ },
326
+ {
327
+ label: "test",
328
+ value: "test"
329
+ }
330
+ ]
331
+ });
332
+ if (p3.isCancel(type)) {
333
+ p3.log.error(color3.red("nothing selected!"));
334
+ return await exit(1);
335
+ }
336
+ options.type = type;
337
+ }
275
338
  spin.start("generating commit message");
276
- const message = await generateCommitMessage(diff);
277
- spin.stop(color3.green(message));
339
+ let message = await generateCommitMessage(diff, options.type);
340
+ spin.stop(isEditMode ? color3.white(message) : color3.green(message));
341
+ if (isEditMode) {
342
+ const editedMessage = await p3.text({
343
+ message: "edit the generated commit message",
344
+ initialValue: message,
345
+ placeholder: message
346
+ });
347
+ if (p3.isCancel(editedMessage)) {
348
+ p3.log.error(color3.red("nothing changed!"));
349
+ return await exit(1);
350
+ }
351
+ message = editedMessage;
352
+ p3.log.step(color3.green(message));
353
+ }
354
+ await StorageManager.update((current) => ({
355
+ ...current,
356
+ lastGeneratedMessage: message
357
+ }));
278
358
  if (options["--copy"]) {
279
359
  clipboard.writeSync(message);
280
360
  p3.log.step(color3.dim("copied commit message to clipboard"));
@@ -287,44 +367,135 @@ var command = {
287
367
  p3.log.error(color3.red("failed to commit changes"));
288
368
  }
289
369
  }
370
+ process.stdout.write("\n");
290
371
  } catch {
291
372
  spin.stop(color3.red("failed to generate commit message"), 1);
292
- process.exit(1);
293
- } finally {
294
- process.stdout.write("\n");
373
+ return await exit(1);
295
374
  }
296
375
  })
297
376
  )
298
377
  };
299
378
  var noto_default = command;
300
379
 
301
- // src/commands/config.ts
380
+ // src/commands/prev.ts
302
381
  import * as p4 from "@clack/prompts";
303
382
  import color4 from "picocolors";
383
+ import dedent4 from "dedent";
384
+ import clipboard2 from "clipboardy";
385
+ var command2 = {
386
+ name: "prev",
387
+ description: "access the last generated commit message",
388
+ usage: "noto prev [options]",
389
+ options: [
390
+ {
391
+ type: Boolean,
392
+ flag: "--copy",
393
+ alias: "-c",
394
+ description: "copy the last generated commit message to clipboard"
395
+ },
396
+ {
397
+ type: Boolean,
398
+ flag: "--apply",
399
+ alias: "-a",
400
+ description: "commit the last generated message directly"
401
+ },
402
+ {
403
+ type: Boolean,
404
+ flag: "--edit",
405
+ alias: "-e",
406
+ description: "edit the last generated commit message"
407
+ }
408
+ ],
409
+ execute: withAuth(
410
+ withRepository(
411
+ async (options) => {
412
+ let lastGeneratedMessage = (await StorageManager.get()).lastGeneratedMessage;
413
+ if (!lastGeneratedMessage) {
414
+ p4.log.error(color4.red("no previous commit message found"));
415
+ return await exit(1);
416
+ }
417
+ const isEditMode = options["--edit"];
418
+ p4.log.step(
419
+ isEditMode ? color4.white(lastGeneratedMessage) : color4.green(lastGeneratedMessage)
420
+ );
421
+ if (options["--edit"]) {
422
+ const editedMessage = await p4.text({
423
+ message: "edit the last generated commit message",
424
+ initialValue: lastGeneratedMessage,
425
+ placeholder: lastGeneratedMessage
426
+ });
427
+ if (p4.isCancel(editedMessage)) {
428
+ p4.log.error(color4.red("nothing changed!"));
429
+ return await exit(1);
430
+ }
431
+ lastGeneratedMessage = editedMessage;
432
+ await StorageManager.update((current) => ({
433
+ ...current,
434
+ lastGeneratedMessage: editedMessage
435
+ }));
436
+ p4.log.step(color4.green(lastGeneratedMessage));
437
+ }
438
+ if (options["--copy"]) {
439
+ clipboard2.writeSync(lastGeneratedMessage);
440
+ p4.log.step(
441
+ color4.dim("copied last generated commit message to clipboard")
442
+ );
443
+ }
444
+ if (options["--apply"]) {
445
+ if (!options.isRepo) {
446
+ p4.log.error(
447
+ dedent4`${color4.red("no git repository found in cwd.")}
448
+ ${color4.dim(`run ${color4.cyan("`git init`")} to initialize a new repository.`)}`
449
+ );
450
+ return await exit(1);
451
+ }
452
+ if (!options.diff) {
453
+ p4.log.error(
454
+ dedent4`${color4.red("no staged changes found.")}
455
+ ${color4.dim(`run ${color4.cyan("`git add <file>`")} or ${color4.cyan("`git add .`")} to stage changes.`)}`
456
+ );
457
+ return await exit(1);
458
+ }
459
+ const success = await commit(lastGeneratedMessage);
460
+ if (success) {
461
+ p4.log.step(color4.dim("commit successful"));
462
+ } else {
463
+ p4.log.error(color4.red("failed to commit changes"));
464
+ }
465
+ }
466
+ console.log();
467
+ },
468
+ { silent: true }
469
+ )
470
+ )
471
+ };
472
+ var prev_default = command2;
473
+
474
+ // src/commands/config.ts
475
+ import * as p5 from "@clack/prompts";
476
+ import color5 from "picocolors";
304
477
  var key = {
305
478
  name: "key",
306
479
  description: "configure api key",
307
480
  usage: "noto config key [options]",
308
481
  execute: async (options) => {
309
482
  if ((await StorageManager.get()).llm?.apiKey) {
310
- const confirm2 = await p4.confirm({
483
+ const confirm2 = await p5.confirm({
311
484
  message: "noto api key already configured, do you want to update it?"
312
485
  });
313
- if (p4.isCancel(confirm2) || !confirm2) {
314
- p4.log.error(color4.red("nothing changed!"));
315
- console.log();
316
- process.exit(1);
486
+ if (p5.isCancel(confirm2) || !confirm2) {
487
+ p5.log.error(color5.red("nothing changed!"));
488
+ return await exit(1);
317
489
  }
318
490
  }
319
491
  let apiKey = options._[0];
320
492
  if (!apiKey) {
321
- const result = await p4.text({
493
+ const result = await p5.text({
322
494
  message: "enter your noto api key"
323
495
  });
324
- if (p4.isCancel(result)) {
325
- p4.log.error(color4.red("nothing changed!"));
326
- console.log();
327
- process.exit(1);
496
+ if (p5.isCancel(result)) {
497
+ p5.log.error(color5.red("nothing changed!"));
498
+ return await exit(1);
328
499
  }
329
500
  apiKey = result;
330
501
  }
@@ -335,7 +506,7 @@ var key = {
335
506
  apiKey
336
507
  }
337
508
  }));
338
- p4.log.success(color4.green("noto api key configured!"));
509
+ p5.log.success(color5.green("noto api key configured!"));
339
510
  console.log();
340
511
  }
341
512
  };
@@ -344,7 +515,7 @@ var model = {
344
515
  description: "configure model",
345
516
  usage: "noto config model [options]",
346
517
  execute: async (options) => {
347
- const model2 = await p4.select({
518
+ const model2 = await p5.select({
348
519
  message: "select a model",
349
520
  initialValue: (await StorageManager.get()).llm?.model,
350
521
  options: Object.keys(models).map((model3) => ({
@@ -352,10 +523,9 @@ var model = {
352
523
  value: model3
353
524
  }))
354
525
  });
355
- if (p4.isCancel(model2)) {
356
- p4.log.error(color4.red("nothing changed!"));
357
- console.log();
358
- process.exit();
526
+ if (p5.isCancel(model2)) {
527
+ p5.log.error(color5.red("nothing changed!"));
528
+ return await exit(1);
359
529
  }
360
530
  await StorageManager.update((current) => ({
361
531
  ...current,
@@ -364,48 +534,46 @@ var model = {
364
534
  model: model2
365
535
  }
366
536
  }));
367
- p4.log.success(color4.green("model configured!"));
537
+ p5.log.success(color5.green("model configured!"));
368
538
  console.log();
369
539
  }
370
540
  };
371
541
  var subCommands = [key, model];
372
- var command2 = {
542
+ var command3 = {
373
543
  name: "config",
374
544
  description: "configure noto",
375
545
  usage: "noto config [subcommand]",
376
546
  execute: async (options) => {
377
- const command3 = await p4.select({
547
+ const command4 = await p5.select({
378
548
  message: "Select a subcommand",
379
549
  options: subCommands.map((cmd2) => ({
380
550
  label: cmd2.description,
381
551
  value: cmd2.name
382
552
  }))
383
553
  });
384
- if (p4.isCancel(command3)) {
385
- console.log();
386
- process.exit(1);
554
+ if (p5.isCancel(command4)) {
555
+ return await exit(1);
387
556
  }
388
- const cmd = getCommand(command3, subCommands);
557
+ const cmd = getCommand(command4, subCommands);
389
558
  if (!cmd) {
390
- p4.log.error(color4.red("unknown config command"));
391
- console.log();
392
- process.exit(1);
559
+ p5.log.error(color5.red("unknown config command"));
560
+ return await exit(1);
393
561
  }
394
562
  options._ = options._.slice(1);
395
563
  cmd.execute(options);
396
564
  },
397
565
  subCommands
398
566
  };
399
- var config_default = command2;
567
+ var config_default = command3;
400
568
 
401
569
  // src/commands/index.ts
402
- var commands = [noto_default, config_default];
570
+ var commands = [noto_default, prev_default, config_default];
403
571
  var getCommand = (name, cmds = commands) => {
404
572
  return cmds.find((cmd) => cmd.name === name);
405
573
  };
406
574
 
407
575
  // package.json
408
- var version = "1.0.0-beta.3";
576
+ var version = "1.0.0-beta.4";
409
577
 
410
578
  // src/index.ts
411
579
  var globalSpec = {
@@ -416,16 +584,16 @@ var globalSpec = {
416
584
  };
417
585
  function main() {
418
586
  const args = process.argv.slice(2);
419
- const { command: command3, options: globalOptions } = parse(globalSpec, args);
587
+ const { command: command4, options: globalOptions } = parse(globalSpec, args);
420
588
  console.log();
421
- p5.intro(`${color5.bgCyan(color5.black(" @snelusha/noto "))}`);
422
- if (globalOptions["--version"]) return p5.outro(version);
423
- const cmd = getCommand(command3) ?? getCommand("noto");
589
+ p6.intro(`${color6.bgCyan(color6.black(" @snelusha/noto "))}`);
590
+ if (globalOptions["--version"]) return p6.outro(version);
591
+ const cmd = getCommand(command4) ?? getCommand("noto");
424
592
  if (!cmd) return getCommand("noto")?.execute(globalOptions);
425
593
  let commandArgs = args;
426
594
  let selectedCommand = cmd;
427
595
  if (cmd.subCommands && commandArgs.length) {
428
- const possibleCommand = commandArgs[0];
596
+ const possibleCommand = commandArgs[1];
429
597
  const subCommand = cmd.subCommands.find(
430
598
  (cmd2) => cmd2.name === possibleCommand || cmd2.aliases && cmd2.aliases.includes(possibleCommand)
431
599
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snelusha/noto",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Generate clean commit messages in a snap! ✨",
5
5
  "license": "MIT",
6
6
  "type": "module",