uva-cli 1.0.0 → 1.1.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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/uva-cli-lockup.svg" alt="uva-cli" width="420" />
2
+ <img src="https://raw.githubusercontent.com/ihFernando/uva-cli/main/assets/uva-cli-lockup.svg" alt="uva-cli" width="420" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -4,20 +4,20 @@ import { hasUncommittedChanges, checkout, pull, createBranch } from '../lib/git.
4
4
  import { COMMIT_TYPES } from '../lib/types.mjs'
5
5
  import { requireConfig } from '../lib/config.mjs'
6
6
  import { buildBranchName } from '../lib/format.mjs'
7
+ import { getLocale } from '../lib/i18n.mjs'
7
8
 
8
9
  // Called by commander as runBranch(options, command)
9
10
  export async function runBranch(opts = {}) {
10
11
  bannerIntro('branch')
11
12
 
12
13
  const config = requireConfig()
14
+ const t = getLocale(config)
13
15
 
14
16
  if (hasUncommittedChanges()) {
15
- const proceed = await confirm({
16
- message: 'You have uncommitted changes. They will carry over to the new branch. Continue?',
17
- })
17
+ const proceed = await confirm({ message: t.branch.uncommittedConfirm })
18
18
  if (isCancel(proceed) || !proceed) {
19
- log.info('Tip: run `uva commit` first to commit pending changes.')
20
- bannerCancelled()
19
+ log.info(t.branch.uncommittedTip)
20
+ bannerCancelled(t.common.cancelled)
21
21
  process.exit(0)
22
22
  }
23
23
  }
@@ -26,16 +26,16 @@ export async function runBranch(opts = {}) {
26
26
  let source
27
27
  if (opts.source) {
28
28
  if (!config.branch.sources.includes(opts.source)) {
29
- log.warn(`"${opts.source}" is not in the configured source branches. Proceeding anyway.`)
29
+ log.warn(t.branch.sourceWarn(opts.source))
30
30
  }
31
31
  source = opts.source
32
32
  } else {
33
33
  source = await select({
34
- message: 'Branch from',
34
+ message: t.branch.source,
35
35
  options: config.branch.sources.map((b) => ({ value: b, label: b })),
36
36
  })
37
37
  if (isCancel(source)) {
38
- bannerCancelled()
38
+ bannerCancelled(t.common.cancelled)
39
39
  process.exit(0)
40
40
  }
41
41
  }
@@ -47,12 +47,12 @@ export async function runBranch(opts = {}) {
47
47
  ticket = opts.ticket
48
48
  } else {
49
49
  const input = await text({
50
- message: 'Ticket',
50
+ message: t.branch.ticket,
51
51
  placeholder: config.commit.ticketPlaceholder,
52
- validate: (v) => (v.trim() ? undefined : 'Ticket cannot be empty.'),
52
+ validate: (v) => (v.trim() ? undefined : t.branch.ticketError),
53
53
  })
54
54
  if (isCancel(input)) {
55
- bannerCancelled()
55
+ bannerCancelled(t.common.cancelled)
56
56
  process.exit(0)
57
57
  }
58
58
  ticket = input.trim()
@@ -64,14 +64,14 @@ export async function runBranch(opts = {}) {
64
64
  if (opts.type) {
65
65
  const valid = COMMIT_TYPES.map((t) => t.value)
66
66
  if (!valid.includes(opts.type)) {
67
- log.error(`Invalid --type "${opts.type}". Valid values: ${valid.join(', ')}`)
67
+ log.error(t.branch.invalidType(opts.type, valid.join(', ')))
68
68
  process.exit(1)
69
69
  }
70
70
  type = opts.type
71
71
  } else {
72
- type = await select({ message: 'Branch type', options: COMMIT_TYPES })
72
+ type = await select({ message: t.branch.type, options: COMMIT_TYPES })
73
73
  if (isCancel(type)) {
74
- bannerCancelled()
74
+ bannerCancelled(t.common.cancelled)
75
75
  process.exit(0)
76
76
  }
77
77
  }
@@ -82,12 +82,12 @@ export async function runBranch(opts = {}) {
82
82
  taskName = opts.name
83
83
  } else {
84
84
  const input = await text({
85
- message: 'Task name',
86
- placeholder: 'add login screen',
87
- validate: (v) => (v.trim() ? undefined : 'Task name cannot be empty.'),
85
+ message: t.branch.name,
86
+ placeholder: t.branch.namePlaceholder,
87
+ validate: (v) => (v.trim() ? undefined : t.branch.nameError),
88
88
  })
89
89
  if (isCancel(input)) {
90
- bannerCancelled()
90
+ bannerCancelled(t.common.cancelled)
91
91
  process.exit(0)
92
92
  }
93
93
  taskName = input.trim()
@@ -100,9 +100,9 @@ export async function runBranch(opts = {}) {
100
100
  const scripted = opts.source && opts.type && opts.name && ticketDone
101
101
 
102
102
  if (!scripted) {
103
- const confirmed = await confirm({ message: `Create branch:\n ${branchName}` })
103
+ const confirmed = await confirm({ message: t.branch.confirm(branchName) })
104
104
  if (isCancel(confirmed) || !confirmed) {
105
- bannerCancelled()
105
+ bannerCancelled(t.common.cancelled)
106
106
  process.exit(0)
107
107
  }
108
108
  }
@@ -110,16 +110,14 @@ export async function runBranch(opts = {}) {
110
110
  try {
111
111
  checkout(source)
112
112
  } catch {
113
- log.error(
114
- `Could not switch to ${source}. Make sure the branch exists and there are no conflicts.`,
115
- )
113
+ log.error(t.branch.checkoutError(source))
116
114
  process.exit(1)
117
115
  }
118
116
 
119
117
  try {
120
118
  pull()
121
119
  } catch {
122
- log.error('Could not pull. Check your connection or resolve any conflicts.')
120
+ log.error(t.branch.pullError)
123
121
  process.exit(1)
124
122
  }
125
123
 
@@ -127,12 +125,12 @@ export async function runBranch(opts = {}) {
127
125
  createBranch(branchName)
128
126
  } catch (err) {
129
127
  if (err.stderr?.includes('already exists') || err.message?.includes('already exists')) {
130
- log.error(`Branch "${branchName}" already exists. Choose a different name or ticket.`)
128
+ log.error(t.branch.branchExists(branchName))
131
129
  } else {
132
- log.error('Could not create the branch. Check if the name is valid.')
130
+ log.error(t.branch.createError)
133
131
  }
134
132
  process.exit(1)
135
133
  }
136
134
 
137
- bannerOutro(`Branch created: ${branchName}`)
135
+ bannerOutro(t.branch.done(branchName))
138
136
  }
@@ -4,16 +4,18 @@ import { getChangedFiles, addFiles, commit } from '../lib/git.mjs'
4
4
  import { COMMIT_TYPES } from '../lib/types.mjs'
5
5
  import { requireConfig } from '../lib/config.mjs'
6
6
  import { buildCommitMessage } from '../lib/format.mjs'
7
+ import { getLocale } from '../lib/i18n.mjs'
7
8
 
8
9
  // Called by commander as runCommit(options, command)
9
10
  export async function runCommit(opts = {}) {
10
11
  bannerIntro('commit')
11
12
 
12
13
  const config = requireConfig()
14
+ const t = getLocale(config)
13
15
  const files = getChangedFiles()
14
16
 
15
17
  if (files.length === 0) {
16
- log.info('Nothing to commit here.')
18
+ log.info(t.commit.nothingToCommit)
17
19
  process.exit(0)
18
20
  }
19
21
 
@@ -22,14 +24,14 @@ export async function runCommit(opts = {}) {
22
24
  if (opts.type) {
23
25
  const valid = COMMIT_TYPES.map((t) => t.value)
24
26
  if (!valid.includes(opts.type)) {
25
- log.error(`Invalid --type "${opts.type}". Valid values: ${valid.join(', ')}`)
27
+ log.error(t.commit.invalidType(opts.type, valid.join(', ')))
26
28
  process.exit(1)
27
29
  }
28
30
  type = opts.type
29
31
  } else {
30
- type = await select({ message: 'Commit type', options: COMMIT_TYPES })
32
+ type = await select({ message: t.commit.type, options: COMMIT_TYPES })
31
33
  if (isCancel(type)) {
32
- bannerCancelled()
34
+ bannerCancelled(t.common.cancelled)
33
35
  process.exit(0)
34
36
  }
35
37
  }
@@ -41,12 +43,12 @@ export async function runCommit(opts = {}) {
41
43
  ticket = opts.ticket
42
44
  } else {
43
45
  const input = await text({
44
- message: 'Ticket',
46
+ message: t.commit.ticket,
45
47
  placeholder: config.commit.ticketPlaceholder,
46
- validate: (v) => (v.trim() ? undefined : 'Ticket cannot be empty.'),
48
+ validate: (v) => (v.trim() ? undefined : t.commit.ticketError),
47
49
  })
48
50
  if (isCancel(input)) {
49
- bannerCancelled()
51
+ bannerCancelled(t.common.cancelled)
50
52
  process.exit(0)
51
53
  }
52
54
  ticket = input.trim()
@@ -64,21 +66,19 @@ export async function runCommit(opts = {}) {
64
66
  message = opts.message.trim().toLowerCase()
65
67
  } else {
66
68
  const input = await text({
67
- message: 'Commit message',
68
- placeholder: 'add login screen',
69
- validate: (v) => (v.trim() ? undefined : 'Message cannot be empty.'),
69
+ message: t.commit.message,
70
+ placeholder: t.commit.messagePlaceholder,
71
+ validate: (v) => (v.trim() ? undefined : t.commit.messageError),
70
72
  })
71
73
  if (isCancel(input)) {
72
- bannerCancelled()
74
+ bannerCancelled(t.common.cancelled)
73
75
  process.exit(0)
74
76
  }
75
77
  message = input.trim().toLowerCase()
76
78
  }
77
79
 
78
80
  if (message.length > 72) {
79
- log.warn(
80
- 'Message is long (over 72 characters). Shorter messages are recommended, but you can continue.',
81
- )
81
+ log.warn(t.commit.messageLong)
82
82
  }
83
83
 
84
84
  // Files
@@ -92,20 +92,30 @@ export async function runCommit(opts = {}) {
92
92
  const availablePaths = files.map((f) => f.path)
93
93
  const invalid = paths.filter((p) => !availablePaths.includes(p))
94
94
  if (invalid.length) {
95
- log.error(`Unknown file(s): ${invalid.join(', ')}\nAvailable: ${availablePaths.join(', ')}`)
95
+ log.error(t.commit.unknownFiles(invalid.join(', '), availablePaths.join(', ')))
96
96
  process.exit(1)
97
97
  }
98
98
  selected = paths
99
99
  } else {
100
+ const fileOptions = files.map((f) => ({
101
+ value: f.path,
102
+ label: `${f.status.padEnd(2)} ${f.path}`,
103
+ }))
104
+ if (files.length >= 5) {
105
+ fileOptions.unshift({ value: '__all__', label: t.commit.allFiles })
106
+ }
100
107
  selected = await multiselect({
101
- message: 'Which files to include in the commit?',
102
- options: files.map((f) => ({ value: f.path, label: `${f.status.padEnd(2)} ${f.path}` })),
108
+ message: t.commit.files,
109
+ options: fileOptions,
103
110
  required: true,
104
111
  })
105
112
  if (isCancel(selected)) {
106
- bannerCancelled()
113
+ bannerCancelled(t.common.cancelled)
107
114
  process.exit(0)
108
115
  }
116
+ if (selected.includes('__all__')) {
117
+ selected = files.map((f) => f.path)
118
+ }
109
119
  }
110
120
 
111
121
  finalMessage = buildCommitMessage(config.commit.format, { type, ticket, message })
@@ -114,23 +124,23 @@ export async function runCommit(opts = {}) {
114
124
  const scripted = opts.type && opts.message && (opts.all || opts.files)
115
125
  if (scripted) break
116
126
 
117
- const confirmed = await confirm({ message: `Commit with message:\n ${finalMessage}` })
127
+ const confirmed = await confirm({ message: t.commit.confirm(finalMessage) })
118
128
  if (isCancel(confirmed)) {
119
- bannerCancelled()
129
+ bannerCancelled(t.common.cancelled)
120
130
  process.exit(0)
121
131
  }
122
132
  if (confirmed) break
123
133
 
124
134
  if (opts.message) break // message is fixed, can't retry
125
- log.info("OK! Let's rewrite the message.")
135
+ log.info(t.commit.retry)
126
136
  }
127
137
 
128
138
  try {
129
139
  addFiles(selected)
130
140
  commit(finalMessage)
131
- bannerOutro(`Committed: ${finalMessage}`)
141
+ bannerOutro(t.commit.done(finalMessage))
132
142
  } catch (e) {
133
- log.error(`Commit failed: ${e.message}`)
143
+ log.error(t.commit.failed(e.message))
134
144
  process.exit(1)
135
145
  }
136
146
  }
package/commands/init.mjs CHANGED
@@ -2,36 +2,48 @@ import { text, select, confirm, isCancel } from '@clack/prompts'
2
2
  import { bannerIntro, bannerOutro, bannerCancelled } from '../lib/banner.mjs'
3
3
  import { loadConfig, saveConfig } from '../lib/config.mjs'
4
4
  import { COMMIT_FORMATS, BRANCH_FORMATS } from '../lib/types.mjs'
5
+ import { getLocale } from '../lib/i18n.mjs'
5
6
 
6
7
  export async function runInit() {
7
8
  bannerIntro('init')
8
9
 
10
+ // Language is always the first question — no config exists yet
11
+ const lang = await select({
12
+ message: 'Language / Idioma',
13
+ options: [
14
+ { value: 'en', label: 'English' },
15
+ { value: 'pt-br', label: 'Português (BR)' },
16
+ ],
17
+ })
18
+ if (isCancel(lang)) {
19
+ bannerCancelled()
20
+ process.exit(0)
21
+ }
22
+
23
+ const t = getLocale({ project: { lang } })
24
+
9
25
  const existing = loadConfig()
10
26
  if (existing) {
11
- const overwrite = await confirm({
12
- message: 'A configuration already exists. Do you want to overwrite it?',
13
- })
27
+ const overwrite = await confirm({ message: t.init.overwriteConfirm })
14
28
  if (isCancel(overwrite) || !overwrite) {
15
- bannerCancelled()
29
+ bannerCancelled(t.common.cancelled)
16
30
  process.exit(0)
17
31
  }
18
32
  }
19
33
 
20
34
  const projectName = await text({
21
- message: 'Project name',
22
- placeholder: 'My Awesome Project',
23
- validate: (v) => (v.trim() ? undefined : 'Project name cannot be empty.'),
35
+ message: t.init.projectName,
36
+ placeholder: t.init.projectNamePlaceholder,
37
+ validate: (v) => (v.trim() ? undefined : t.init.projectNameError),
24
38
  })
25
39
  if (isCancel(projectName)) {
26
- bannerCancelled()
40
+ bannerCancelled(t.common.cancelled)
27
41
  process.exit(0)
28
42
  }
29
43
 
30
- const useTicket = await confirm({
31
- message: 'Do you use a ticket/issue tracker? (e.g., Jira, Linear, GitHub Issues)',
32
- })
44
+ const useTicket = await confirm({ message: t.init.ticketConfirm })
33
45
  if (isCancel(useTicket)) {
34
- bannerCancelled()
46
+ bannerCancelled(t.common.cancelled)
35
47
  process.exit(0)
36
48
  }
37
49
 
@@ -40,12 +52,12 @@ export async function runInit() {
40
52
 
41
53
  if (useTicket) {
42
54
  const prefix = await text({
43
- message: 'Ticket prefix',
44
- placeholder: 'PROJ',
45
- validate: (v) => (v.trim() ? undefined : 'Ticket prefix cannot be empty.'),
55
+ message: t.init.ticketPrefix,
56
+ placeholder: t.init.ticketPrefixPlaceholder,
57
+ validate: (v) => (v.trim() ? undefined : t.init.ticketPrefixError),
46
58
  })
47
59
  if (isCancel(prefix)) {
48
- bannerCancelled()
60
+ bannerCancelled(t.common.cancelled)
49
61
  process.exit(0)
50
62
  }
51
63
  ticketPrefix = prefix.trim().toUpperCase()
@@ -55,33 +67,33 @@ export async function runInit() {
55
67
  let commitFormat = 'conventional'
56
68
  if (useTicket) {
57
69
  const selectedCommitFormat = await select({
58
- message: 'Choose a commit message format',
70
+ message: t.init.commitFormat,
59
71
  options: COMMIT_FORMATS.map((f) => ({
60
72
  ...f,
61
73
  label: f.label.replace(/PROJ/g, ticketPrefix || 'PROJ'),
62
74
  })),
63
75
  })
64
76
  if (isCancel(selectedCommitFormat)) {
65
- bannerCancelled()
77
+ bannerCancelled(t.common.cancelled)
66
78
  process.exit(0)
67
79
  }
68
80
  commitFormat = selectedCommitFormat
69
81
  }
70
82
 
71
83
  const sourcesInput = await text({
72
- message: 'Branches to branch from (comma-separated)',
73
- placeholder: 'main,develop',
84
+ message: t.init.sources,
85
+ placeholder: t.init.sourcesPlaceholder,
74
86
  initialValue: 'main,develop',
75
87
  validate: (v) => {
76
88
  const parts = v
77
89
  .split(',')
78
90
  .map((s) => s.trim())
79
91
  .filter(Boolean)
80
- return parts.length > 0 ? undefined : 'At least one branch is required.'
92
+ return parts.length > 0 ? undefined : t.init.sourcesError
81
93
  },
82
94
  })
83
95
  if (isCancel(sourcesInput)) {
84
- bannerCancelled()
96
+ bannerCancelled(t.common.cancelled)
85
97
  process.exit(0)
86
98
  }
87
99
  const sources = sourcesInput
@@ -92,44 +104,44 @@ export async function runInit() {
92
104
  let branchFormat = 'type-name'
93
105
  if (useTicket) {
94
106
  const selectedBranchFormat = await select({
95
- message: 'Choose a branch naming format',
107
+ message: t.init.branchFormat,
96
108
  options: BRANCH_FORMATS.map((f) => ({
97
109
  ...f,
98
110
  label: f.label.replace(/PROJ/g, ticketPrefix || 'PROJ'),
99
111
  })),
100
112
  })
101
113
  if (isCancel(selectedBranchFormat)) {
102
- bannerCancelled()
114
+ bannerCancelled(t.common.cancelled)
103
115
  process.exit(0)
104
116
  }
105
117
  branchFormat = selectedBranchFormat
106
118
  }
107
119
 
108
120
  const useArea = await confirm({
109
- message: 'Do you want to categorize branches by area? (e.g., FE, BE, DOC)',
121
+ message: t.init.areaConfirm,
110
122
  initialValue: false,
111
123
  })
112
124
  if (isCancel(useArea)) {
113
- bannerCancelled()
125
+ bannerCancelled(t.common.cancelled)
114
126
  process.exit(0)
115
127
  }
116
128
 
117
129
  let areas = []
118
130
  if (useArea) {
119
131
  const areasInput = await text({
120
- message: 'Area labels (comma-separated)',
121
- placeholder: 'FE,BE,DOC',
132
+ message: t.init.areaLabels,
133
+ placeholder: t.init.areaLabelsPlaceholder,
122
134
  initialValue: 'FE,BE,DOC',
123
135
  validate: (v) => {
124
136
  const parts = v
125
137
  .split(',')
126
138
  .map((s) => s.trim())
127
139
  .filter(Boolean)
128
- return parts.length > 0 ? undefined : 'At least one area is required.'
140
+ return parts.length > 0 ? undefined : t.init.areaLabelsError
129
141
  },
130
142
  })
131
143
  if (isCancel(areasInput)) {
132
- bannerCancelled()
144
+ bannerCancelled(t.common.cancelled)
133
145
  process.exit(0)
134
146
  }
135
147
  areas = areasInput
@@ -139,7 +151,7 @@ export async function runInit() {
139
151
  }
140
152
 
141
153
  const config = {
142
- project: { name: projectName.trim() },
154
+ project: { name: projectName.trim(), lang },
143
155
  commit: {
144
156
  ticketEnabled: Boolean(useTicket),
145
157
  ticketPrefix,
@@ -156,5 +168,5 @@ export async function runInit() {
156
168
  }
157
169
 
158
170
  const configPath = saveConfig(config)
159
- bannerOutro(`Configuration saved to ${configPath}`)
171
+ bannerOutro(t.init.saved(configPath))
160
172
  }
package/commands/push.mjs CHANGED
@@ -1,20 +1,21 @@
1
1
  import { confirm, isCancel, log } from '@clack/prompts'
2
2
  import { bannerIntro, bannerOutro, bannerCancelled } from '../lib/banner.mjs'
3
3
  import { getCurrentBranch } from '../lib/git.mjs'
4
+ import { loadConfig } from '../lib/config.mjs'
5
+ import { getLocale } from '../lib/i18n.mjs'
4
6
  import { spawnSync } from 'child_process'
5
7
 
6
8
  // Called by commander as runPush(options, command)
7
9
  export async function runPush(opts = {}) {
8
10
  bannerIntro('push')
9
11
 
12
+ const t = getLocale(loadConfig())
10
13
  const branch = getCurrentBranch()
11
14
 
12
15
  if (!opts.yes) {
13
- const confirmed = await confirm({
14
- message: `Push current branch: ${branch}?`,
15
- })
16
+ const confirmed = await confirm({ message: t.push.confirm(branch) })
16
17
  if (isCancel(confirmed) || !confirmed) {
17
- bannerCancelled()
18
+ bannerCancelled(t.common.cancelled)
18
19
  process.exit(0)
19
20
  }
20
21
  }
@@ -23,9 +24,9 @@ export async function runPush(opts = {}) {
23
24
  stdio: ['pipe', 'inherit', 'inherit'],
24
25
  })
25
26
  if (result.status !== 0) {
26
- log.error('Push failed — see the output above.')
27
+ log.error(t.push.failed)
27
28
  process.exit(1)
28
29
  }
29
30
 
30
- bannerOutro(`Pushed: origin/${branch}`)
31
+ bannerOutro(t.push.done(branch))
31
32
  }
@@ -1,6 +1,8 @@
1
1
  import { select, isCancel, intro, outro } from '@clack/prompts'
2
2
  import pc from 'picocolors'
3
3
  import { banner as brandBanner, uva, folha } from '../lib/colors.mjs'
4
+ import { loadConfig } from '../lib/config.mjs'
5
+ import { getLocale } from '../lib/i18n.mjs'
4
6
  import { runInit } from './init.mjs'
5
7
  import { runCommit } from './commit.mjs'
6
8
  import { runBranch } from './branch.mjs'
@@ -8,48 +10,51 @@ import { runNewFile } from './new-file.mjs'
8
10
  import { runPush } from './push.mjs'
9
11
 
10
12
  export async function runStart() {
13
+ const t = getLocale(loadConfig())
14
+
11
15
  intro(brandBanner('UVA CLI'))
12
16
 
13
17
  console.log('')
14
- console.log(uva(' uva-cli') + pc.dim(' — Git workflow automation'))
18
+ console.log(uva(' uva-cli') + pc.dim(` ${t.start.tagline}`))
15
19
  console.log('')
16
- console.log(pc.dim(' Guides your team through branch creation and commits'))
17
- console.log(pc.dim(' following whatever conventions your project defines.'))
20
+ console.log(pc.dim(` ${t.start.desc1}`))
21
+ console.log(pc.dim(` ${t.start.desc2}`))
18
22
  console.log('')
19
23
 
24
+ const o = t.start.options
20
25
  const action = await select({
21
- message: 'What do you want to do?',
26
+ message: t.start.prompt,
22
27
  options: [
23
28
  {
24
29
  value: 'init',
25
- label: folha('uva init') + ' Set up UVA CLI for this project',
26
- hint: 'configure commit and branch patterns',
30
+ label: folha('uva init') + ' ' + o.init.label,
31
+ hint: o.init.hint,
27
32
  },
28
33
  {
29
34
  value: 'commit',
30
- label: uva('uva commit') + ' Create an interactive commit',
31
- hint: 'select files, type, ticket and message',
35
+ label: uva('uva commit') + ' ' + o.commit.label,
36
+ hint: o.commit.hint,
32
37
  },
33
38
  {
34
39
  value: 'branch',
35
- label: uva('uva branch') + ' Create a new branch',
36
- hint: 'checks out source and pulls automatically',
40
+ label: uva('uva branch') + ' ' + o.branch.label,
41
+ hint: o.branch.hint,
37
42
  },
38
43
  {
39
44
  value: 'new-file',
40
- label: uva('uva new-file') + ' Scaffold a file from a template',
41
- hint: 'docs, frontend (React), or backend (Express)',
45
+ label: uva('uva new-file') + ' ' + o.newFile.label,
46
+ hint: o.newFile.hint,
42
47
  },
43
48
  {
44
49
  value: 'push',
45
- label: uva('uva push') + ' Push the current branch to origin',
46
- hint: 'confirms the branch and runs git push',
50
+ label: uva('uva push') + ' ' + o.push.label,
51
+ hint: o.push.hint,
47
52
  },
48
53
  ],
49
54
  })
50
55
 
51
56
  if (isCancel(action)) {
52
- outro(pc.dim('Operation cancelled.'))
57
+ outro(pc.dim(t.common.cancelled))
53
58
  process.exit(0)
54
59
  }
55
60
 
package/lib/banner.mjs CHANGED
@@ -10,6 +10,6 @@ export function bannerOutro(message) {
10
10
  outro(folha('+ ' + message))
11
11
  }
12
12
 
13
- export function bannerCancelled() {
14
- outro(pc.dim('Operation cancelled.'))
13
+ export function bannerCancelled(message = 'Operation cancelled.') {
14
+ outro(pc.dim(message))
15
15
  }
package/lib/git.mjs CHANGED
@@ -51,7 +51,7 @@ export function addFiles(paths) {
51
51
  stdio: 'pipe',
52
52
  })
53
53
  if (result.status !== 0) {
54
- throw new Error(result.stderr || 'git add falhou')
54
+ throw new Error(result.stderr || 'git add failed')
55
55
  }
56
56
  }
57
57
 
@@ -62,6 +62,6 @@ export function commit(message) {
62
62
  stdio: ['pipe', 'inherit', 'inherit'],
63
63
  })
64
64
  if (result.status !== 0) {
65
- throw new Error('git commit falhouveja a saída acima')
65
+ throw new Error('git commit failedsee output above')
66
66
  }
67
67
  }
package/lib/i18n.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import en from './locales/en.mjs'
2
+ import ptBr from './locales/pt-br.mjs'
3
+
4
+ const locales = { en, 'pt-br': ptBr }
5
+
6
+ export function getLocale(config) {
7
+ const lang = config?.project?.lang ?? 'en'
8
+ return locales[lang] ?? en
9
+ }
@@ -0,0 +1,96 @@
1
+ export default {
2
+ init: {
3
+ langSelect: 'Language / Idioma',
4
+ overwriteConfirm: 'A configuration already exists. Do you want to overwrite it?',
5
+ projectName: 'Project name',
6
+ projectNamePlaceholder: 'My Awesome Project',
7
+ projectNameError: 'Project name cannot be empty.',
8
+ ticketConfirm: 'Do you use a ticket/issue tracker? (e.g., Jira, Linear, GitHub Issues)',
9
+ ticketPrefix: 'Ticket prefix',
10
+ ticketPrefixPlaceholder: 'PROJ',
11
+ ticketPrefixError: 'Ticket prefix cannot be empty.',
12
+ commitFormat: 'Choose a commit message format',
13
+ sources: 'Branches to branch from (comma-separated)',
14
+ sourcesPlaceholder: 'main,develop',
15
+ sourcesError: 'At least one branch is required.',
16
+ branchFormat: 'Choose a branch naming format',
17
+ areaConfirm: 'Do you want to categorize branches by area? (e.g., FE, BE, DOC)',
18
+ areaLabels: 'Area labels (comma-separated)',
19
+ areaLabelsPlaceholder: 'FE,BE,DOC',
20
+ areaLabelsError: 'At least one area is required.',
21
+ saved: (path) => `Configuration saved to ${path}`,
22
+ },
23
+ commit: {
24
+ nothingToCommit: 'Nothing to commit here.',
25
+ invalidType: (type, valid) => `Invalid --type "${type}". Valid values: ${valid}`,
26
+ unknownFiles: (invalid, available) => `Unknown file(s): ${invalid}\nAvailable: ${available}`,
27
+ type: 'Commit type',
28
+ ticket: 'Ticket',
29
+ ticketError: 'Ticket cannot be empty.',
30
+ message: 'Commit message',
31
+ messagePlaceholder: 'add login screen',
32
+ messageError: 'Message cannot be empty.',
33
+ messageLong:
34
+ 'Message is long (over 72 characters). Shorter messages are recommended, but you can continue.',
35
+ files: 'Which files to include in the commit?',
36
+ allFiles: 'All files',
37
+ confirm: (msg) => `Commit with message:\n ${msg}`,
38
+ retry: "OK! Let's rewrite the message.",
39
+ failed: (err) => `Commit failed: ${err}`,
40
+ done: (msg) => `Committed: ${msg}`,
41
+ },
42
+ branch: {
43
+ uncommittedConfirm:
44
+ 'You have uncommitted changes. They will carry over to the new branch. Continue?',
45
+ uncommittedTip: 'Tip: run `uva commit` first to commit pending changes.',
46
+ source: 'Branch from',
47
+ sourceWarn: (src) => `"${src}" is not in the configured source branches. Proceeding anyway.`,
48
+ ticket: 'Ticket',
49
+ ticketError: 'Ticket cannot be empty.',
50
+ type: 'Branch type',
51
+ invalidType: (type, valid) => `Invalid --type "${type}". Valid values: ${valid}`,
52
+ name: 'Task name',
53
+ namePlaceholder: 'add login screen',
54
+ nameError: 'Task name cannot be empty.',
55
+ confirm: (name) => `Create branch:\n ${name}`,
56
+ checkoutError: (src) =>
57
+ `Could not switch to ${src}. Make sure the branch exists and there are no conflicts.`,
58
+ pullError: 'Could not pull. Check your connection or resolve any conflicts.',
59
+ branchExists: (name) => `Branch "${name}" already exists. Choose a different name or ticket.`,
60
+ createError: 'Could not create the branch. Check if the name is valid.',
61
+ done: (name) => `Branch created: ${name}`,
62
+ },
63
+ push: {
64
+ confirm: (branch) => `Push current branch: ${branch}?`,
65
+ failed: 'Push failed — see the output above.',
66
+ done: (branch) => `Pushed: origin/${branch}`,
67
+ },
68
+ start: {
69
+ tagline: '— Git workflow automation',
70
+ desc1: 'Guides your team through branch creation and commits',
71
+ desc2: 'following whatever conventions your project defines.',
72
+ prompt: 'What do you want to do?',
73
+ options: {
74
+ init: {
75
+ label: 'Set up UVA CLI for this project',
76
+ hint: 'configure commit and branch patterns',
77
+ },
78
+ commit: {
79
+ label: 'Create an interactive commit',
80
+ hint: 'select files, type, ticket and message',
81
+ },
82
+ branch: { label: 'Create a new branch', hint: 'checks out source and pulls automatically' },
83
+ newFile: {
84
+ label: 'Scaffold a file from a template',
85
+ hint: 'docs and git workflow templates',
86
+ },
87
+ push: {
88
+ label: 'Push the current branch to origin',
89
+ hint: 'confirms the branch and runs git push',
90
+ },
91
+ },
92
+ },
93
+ common: {
94
+ cancelled: 'Operation cancelled.',
95
+ },
96
+ }
@@ -0,0 +1,101 @@
1
+ export default {
2
+ init: {
3
+ langSelect: 'Language / Idioma',
4
+ overwriteConfirm: 'Já existe uma configuração. Deseja sobrescrever?',
5
+ projectName: 'Nome do projeto',
6
+ projectNamePlaceholder: 'Meu Projeto Incrível',
7
+ projectNameError: 'O nome do projeto não pode estar vazio.',
8
+ ticketConfirm: 'Você usa um rastreador de tickets? (ex.: Jira, Linear, GitHub Issues)',
9
+ ticketPrefix: 'Prefixo do ticket',
10
+ ticketPrefixPlaceholder: 'PROJ',
11
+ ticketPrefixError: 'O prefixo não pode estar vazio.',
12
+ commitFormat: 'Escolha o formato de mensagem de commit',
13
+ sources: 'Branches de origem (separados por vírgula)',
14
+ sourcesPlaceholder: 'main,develop',
15
+ sourcesError: 'Pelo menos um branch é obrigatório.',
16
+ branchFormat: 'Escolha o formato de nome de branch',
17
+ areaConfirm: 'Deseja categorizar os branches por área? (ex.: FE, BE, DOC)',
18
+ areaLabels: 'Áreas (separadas por vírgula)',
19
+ areaLabelsPlaceholder: 'FE,BE,DOC',
20
+ areaLabelsError: 'Pelo menos uma área é obrigatória.',
21
+ saved: (path) => `Configuração salva em ${path}`,
22
+ },
23
+ commit: {
24
+ nothingToCommit: 'Nada para commitar.',
25
+ invalidType: (type, valid) => `Tipo inválido "${type}". Valores válidos: ${valid}`,
26
+ unknownFiles: (invalid, available) =>
27
+ `Arquivo(s) desconhecido(s): ${invalid}\nDisponíveis: ${available}`,
28
+ type: 'Tipo do commit',
29
+ ticket: 'Ticket',
30
+ ticketError: 'O ticket não pode estar vazio.',
31
+ message: 'Mensagem do commit',
32
+ messagePlaceholder: 'adicionar tela de login',
33
+ messageError: 'A mensagem não pode estar vazia.',
34
+ messageLong:
35
+ 'Mensagem longa (mais de 72 caracteres). Mensagens curtas são recomendadas, mas você pode continuar.',
36
+ files: 'Quais arquivos incluir no commit?',
37
+ allFiles: 'Todos os arquivos',
38
+ confirm: (msg) => `Commitar com a mensagem:\n ${msg}`,
39
+ retry: 'Ok! Vamos reescrever a mensagem.',
40
+ failed: (err) => `Commit falhou: ${err}`,
41
+ done: (msg) => `Commitado: ${msg}`,
42
+ },
43
+ branch: {
44
+ uncommittedConfirm:
45
+ 'Você tem alterações não commitadas. Elas serão levadas para o novo branch. Continuar?',
46
+ uncommittedTip: 'Dica: rode `uva commit` primeiro para commitar as alterações pendentes.',
47
+ source: 'Branch de origem',
48
+ sourceWarn: (src) =>
49
+ `"${src}" não está nos branches de origem configurados. Continuando mesmo assim.`,
50
+ ticket: 'Ticket',
51
+ ticketError: 'O ticket não pode estar vazio.',
52
+ type: 'Tipo do branch',
53
+ invalidType: (type, valid) => `Tipo inválido "${type}". Valores válidos: ${valid}`,
54
+ name: 'Nome da tarefa',
55
+ namePlaceholder: 'adicionar tela de login',
56
+ nameError: 'O nome da tarefa não pode estar vazio.',
57
+ confirm: (name) => `Criar branch:\n ${name}`,
58
+ checkoutError: (src) =>
59
+ `Não foi possível mudar para ${src}. Verifique se o branch existe e não há conflitos.`,
60
+ pullError: 'Não foi possível fazer pull. Verifique sua conexão ou resolva conflitos.',
61
+ branchExists: (name) => `O branch "${name}" já existe. Escolha um nome ou ticket diferente.`,
62
+ createError: 'Não foi possível criar o branch. Verifique se o nome é válido.',
63
+ done: (name) => `Branch criado: ${name}`,
64
+ },
65
+ push: {
66
+ confirm: (branch) => `Fazer push do branch atual: ${branch}?`,
67
+ failed: 'Push falhou — veja a saída acima.',
68
+ done: (branch) => `Push realizado: origin/${branch}`,
69
+ },
70
+ start: {
71
+ tagline: '— Automação de workflow Git',
72
+ desc1: 'Guia seu time na criação de branches e commits',
73
+ desc2: 'seguindo as convenções definidas no seu projeto.',
74
+ prompt: 'O que você quer fazer?',
75
+ options: {
76
+ init: {
77
+ label: 'Configurar o UVA CLI para este projeto',
78
+ hint: 'configura padrões de commit e branch',
79
+ },
80
+ commit: {
81
+ label: 'Criar um commit interativo',
82
+ hint: 'selecione arquivos, tipo, ticket e mensagem',
83
+ },
84
+ branch: {
85
+ label: 'Criar um novo branch',
86
+ hint: 'faz checkout da origem e pull automaticamente',
87
+ },
88
+ newFile: {
89
+ label: 'Gerar um arquivo a partir de um template',
90
+ hint: 'templates de docs e workflow Git',
91
+ },
92
+ push: {
93
+ label: 'Fazer push do branch atual para a origin',
94
+ hint: 'confirma o branch e executa git push',
95
+ },
96
+ },
97
+ },
98
+ common: {
99
+ cancelled: 'Operação cancelada.',
100
+ },
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uva-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "UVA CLI — Interactive Git workflow automation for teams",
5
5
  "type": "module",
6
6
  "bin": {