git-push-time 1.1.3 → 1.1.4

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/lib/config.js CHANGED
@@ -17,7 +17,7 @@ function ensureConfigDir() {
17
17
  }
18
18
 
19
19
  /**
20
- * Đọc toàn bộ file cấu hình người dùng
20
+ * Read the user's complete configuration file.
21
21
  * @returns {Record<string, any>}
22
22
  */
23
23
  export function getConfig() {
@@ -33,7 +33,7 @@ export function getConfig() {
33
33
  }
34
34
 
35
35
  /**
36
- * Lưu cấu hình người dùng
36
+ * Save the user's configuration.
37
37
  * @param {Record<string, any>} data
38
38
  */
39
39
  export function saveConfig(data) {
@@ -43,10 +43,10 @@ export function saveConfig(data) {
43
43
  const merged = { ...current, ...data };
44
44
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), 'utf-8');
45
45
  try {
46
- // Token nhạy cảm: giới hạn quyền đọc chỉ owner (chmod 600)
46
+ // Restrict access to sensitive tokens to the owner (chmod 600).
47
47
  fs.chmodSync(CONFIG_FILE, 0o600);
48
48
  } catch {
49
- // Bỏ qua trên Windows hoặc FS không hỗ trợ chmod
49
+ // Ignore on Windows or filesystems that do not support chmod.
50
50
  }
51
51
  return true;
52
52
  } catch {
@@ -55,7 +55,7 @@ export function saveConfig(data) {
55
55
  }
56
56
 
57
57
  /**
58
- * Lấy danh sách tất cả các tài khoản GitHub đã lưu
58
+ * Get all saved GitHub accounts.
59
59
  * @returns {Array<{ login: string, name: string, token: string, active: boolean, avatar_url?: string }>}
60
60
  */
61
61
  export function getAllGitHubAccounts() {
@@ -76,7 +76,7 @@ export function getAllGitHubAccounts() {
76
76
  }
77
77
  }
78
78
 
79
- // Tương thích ngược với trường `github` cũ nếu chưa migrate
79
+ // Support the legacy `github` field if it has not been migrated.
80
80
  if (result.length === 0 && cfg.github && cfg.github.token) {
81
81
  const login = cfg.github.user?.login || 'default';
82
82
  result.push({
@@ -92,7 +92,7 @@ export function getAllGitHubAccounts() {
92
92
  }
93
93
 
94
94
  /**
95
- * Đặt tài khoản GitHub đang kích hoạt
95
+ * Set the active GitHub account.
96
96
  * @param {string} login
97
97
  */
98
98
  export function setActiveGitHubAccount(login) {
@@ -109,14 +109,14 @@ export function setActiveGitHubAccount(login) {
109
109
  }
110
110
 
111
111
  /**
112
- * Lấy GitHub token từ nhiều nguồn theo thứ tự ưu tiên:
113
- * 1. Biến môi trường GITHUB_TOKEN hoặc GH_TOKEN
114
- * 2. Tài khoản GitHub active trong file config
115
- * 3. Token từ GitHub CLI (`gh auth token`) nếu có
112
+ * Get a GitHub token from the available sources, in priority order:
113
+ * 1. GITHUB_TOKEN or GH_TOKEN environment variable
114
+ * 2. Active GitHub account in the config file
115
+ * 3. GitHub CLI token (`gh auth token`), if available
116
116
  * @returns {{ token: string | null, source: 'env' | 'config' | 'gh-cli' | null, user?: any }}
117
117
  */
118
118
  export function getGitHubToken() {
119
- // 1. Kiểm tra Environment variables
119
+ // 1. Check environment variables.
120
120
  if (process.env.GITHUB_TOKEN && process.env.GITHUB_TOKEN.trim()) {
121
121
  return { token: process.env.GITHUB_TOKEN.trim(), source: 'env' };
122
122
  }
@@ -124,7 +124,7 @@ export function getGitHubToken() {
124
124
  return { token: process.env.GH_TOKEN.trim(), source: 'env' };
125
125
  }
126
126
 
127
- // 2. Kiểm tra File config đã lưu
127
+ // 2. Check the saved config file.
128
128
  const cfg = getConfig();
129
129
  const activeLogin = cfg.activeGitHubLogin;
130
130
  if (activeLogin && cfg.githubAccounts && cfg.githubAccounts[activeLogin]) {
@@ -138,7 +138,7 @@ export function getGitHubToken() {
138
138
  return { token: cfg.github.token.trim(), source: 'config', user: cfg.github.user };
139
139
  }
140
140
 
141
- // 3. Kiểm tra GitHub CLI gh
141
+ // 3. Check the GitHub CLI (`gh`).
142
142
  try {
143
143
  const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
144
144
  if (r.status === 0 && r.stdout && r.stdout.trim()) {
@@ -152,7 +152,7 @@ export function getGitHubToken() {
152
152
  }
153
153
 
154
154
  /**
155
- * Lưu tài khoản GitHub vào file config (hỗ trợ đa tài khoản)
155
+ * Save a GitHub account to the config file (supports multiple accounts).
156
156
  * @param {string} token
157
157
  * @param {object} userInfo
158
158
  */
@@ -190,8 +190,8 @@ export function saveGitHubToken(token, userInfo = null) {
190
190
  }
191
191
 
192
192
  /**
193
- * Xóa tài khoản GitHub khỏi file config
194
- * @param {string} [login] nếu không truyền sẽ xóa tài khoản active
193
+ * Remove a GitHub account from the config file.
194
+ * @param {string} [login] If omitted, removes the active account.
195
195
  */
196
196
  export function removeGitHubToken(login = null) {
197
197
  const current = getConfig();
package/lib/fix.js CHANGED
@@ -5,8 +5,8 @@ import { findRiskyStagedFiles } from './git.js';
5
5
  import { scanStagedContentForSecrets } from './secrets.js';
6
6
 
7
7
  /**
8
- * Danh sách phần mở rộng của file mã nguồn / tài liệu.
9
- * KHÔNG BAO GIỜ tự động thêm các file này vào .gitignore vì sẽ làm mất code dự án!
8
+ * Source-code and documentation file extensions.
9
+ * Never add these files to .gitignore automatically; that could hide project code.
10
10
  */
11
11
  const CODE_EXTENSIONS = new Set([
12
12
  '.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx',
@@ -24,12 +24,12 @@ function isCodeOrDocFile(filePath) {
24
24
  }
25
25
 
26
26
  /**
27
- * Tự fix an toàn khi phát hiện secret:
28
- * 1. git rm --cached -r -f <file> (gỡ khỏi commit, giữ nguyên file trên máy)
29
- * 2. Thêm rule tương ứng vào .gitignore (chỉ áp dụng cho file cấu hình secret, KHÔNG ignore file code)
30
- * 3. Tự động stage lại .gitignore để sẵn sàng commit
27
+ * Safely remediate detected secrets:
28
+ * 1. git rm --cached -r -f <file> (remove from Git while keeping the local file)
29
+ * 2. Add a matching .gitignore rule (only for secret config files, never source code)
30
+ * 3. Stage .gitignore so the rule is ready to commit
31
31
  *
32
- * @param {string[]} files danh sách file cần gỡ (vd ['.env', '.npmrc'])
32
+ * @param {string[]} files Files to unstage (e.g. ['.env', '.npmrc'])
33
33
  * @param {string} cwd
34
34
  * @returns {{ fixed: string[], alreadyClean: string[], failed: {file:string, reason:string}[], gitignoreAdded: string[], codeFilesWithSecrets: string[] }}
35
35
  */
@@ -49,13 +49,13 @@ export function autoFixStagedSecrets(files = [], cwd = process.cwd()) {
49
49
  let ok = false;
50
50
  let errReason = '';
51
51
 
52
- // 1. Thử gỡ khỏi stage bằng git rm --cached -r -f
52
+ // 1. Try to unstage with git rm --cached -r -f.
53
53
  const r = spawnSync('git', ['rm', '--cached', '-r', '-f', '--', normalized], { cwd, encoding: 'utf-8' });
54
54
  if (r.status === 0) {
55
55
  ok = true;
56
56
  } else {
57
57
  const msg = String(r.stderr || r.stdout || '');
58
- // Thử fallback bằng git restore --staged nếu git rm báo pathspec/not matched
58
+ // Fall back to git restore --staged if git rm cannot match the path.
59
59
  const rRestore = spawnSync('git', ['restore', '--staged', '--', normalized], { cwd, encoding: 'utf-8' });
60
60
  if (rRestore.status === 0) {
61
61
  ok = true;
@@ -70,18 +70,18 @@ export function autoFixStagedSecrets(files = [], cwd = process.cwd()) {
70
70
  if (ok) {
71
71
  result.fixed.push(file);
72
72
  } else {
73
- result.failed.push({ file, reason: errReason || 'không gỡ được khỏi stage' });
73
+ result.failed.push({ file, reason: errReason || 'Could not unstage file' });
74
74
  }
75
75
  }
76
76
 
77
- // 2. Phân loại và tạo rule .gitignore cho các file config secret (bỏ qua file code)
77
+ // 2. Add .gitignore rules for secret config files (skip source files).
78
78
  const rules = [];
79
79
  for (const f of unique) {
80
80
  const normalized = f.replace(/\\/g, '/');
81
81
  const base = path.basename(normalized);
82
82
 
83
83
  if (isCodeOrDocFile(normalized)) {
84
- // File mã nguồn có dính secret: gỡ khỏi stage nhưng không cho vào .gitignore
84
+ // Unstage source files containing secrets, but do not add them to .gitignore.
85
85
  result.codeFilesWithSecrets.push(f);
86
86
  continue;
87
87
  }
@@ -127,17 +127,17 @@ export function autoFixStagedSecrets(files = [], cwd = process.cwd()) {
127
127
  fs.appendFileSync(gitignorePath, prefix + toAdd.join('\n') + '\n', 'utf-8');
128
128
  result.gitignoreAdded = toAdd;
129
129
 
130
- // Tự động stage ngay .gitignore để không bị sót khi commit
130
+ // Stage .gitignore so the new rules are not missed when committing.
131
131
  spawnSync('git', ['add', '.gitignore'], { cwd });
132
132
  } catch {
133
- // Bỏ qua lỗi ghi file
133
+ // Ignore file-write errors.
134
134
  }
135
135
  }
136
136
 
137
137
  return result;
138
138
  }
139
139
 
140
- /** Kiểm tra còn gì để commit sau khi auto-fix không */
140
+ /** Check whether anything remains to commit after auto-fix. */
141
141
  export function hasStagedChanges(cwd = process.cwd()) {
142
142
  try {
143
143
  const r = spawnSync('git', ['diff', '--cached', '--name-only'], { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
@@ -149,7 +149,7 @@ export function hasStagedChanges(cwd = process.cwd()) {
149
149
  }
150
150
 
151
151
  /**
152
- * Quét các file secret phổ biến trong thư mục làm việc nhưng chưa được ignore trong .gitignore
152
+ * Find common secret files in the working directory that are not ignored by .gitignore.
153
153
  */
154
154
  export function findRiskyWorkingTreeFiles(cwd = process.cwd()) {
155
155
  const denyNames = [
@@ -171,11 +171,11 @@ export function findRiskyWorkingTreeFiles(cwd = process.cwd()) {
171
171
  }
172
172
 
173
173
  /**
174
- * Tìm upstream ref của branch hiện tại (VD: origin/main hoặc origin/master)
174
+ * Find the upstream ref for the current branch (e.g. origin/main or origin/master).
175
175
  */
176
176
  export function getUpstreamRef(branch = 'main', cwd = process.cwd()) {
177
177
  const cleanBranch = String(branch || 'main').trim() || 'main';
178
- // Chặn ký tự shell/git nguy hiểm trước khi truyền (dù đã dùng spawnSync mảng args)
178
+ // Reject unsafe Git ref characters before passing the value to Git.
179
179
  if (/[\s~^:?*\[\\@{]/.test(cleanBranch) || cleanBranch.includes('..')) return null;
180
180
  try {
181
181
  const r = spawnSync('git', ['rev-parse', '--abbrev-ref', `${cleanBranch}@{u}`], { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
@@ -196,7 +196,7 @@ export function getUpstreamRef(branch = 'main', cwd = process.cwd()) {
196
196
  }
197
197
 
198
198
  /**
199
- * Lấy danh sách các commit unpushed (chưa đẩy lên remote)
199
+ * Get the list of unpushed commits.
200
200
  */
201
201
  export function getUnpushedCommits(branch = 'main', cwd = process.cwd()) {
202
202
  const upstream = getUpstreamRef(branch, cwd);
@@ -219,8 +219,8 @@ export function getUnpushedCommits(branch = 'main', cwd = process.cwd()) {
219
219
  }
220
220
 
221
221
  /**
222
- * Squash các commit chưa push thành 1 commit duy nhất sạch sẽ,
223
- * loại bỏ hoàn toàn dấu vết secret ở các commit trung gian (ví dụ commit cũ chứa .npmrc hay .env).
222
+ * Squash unpushed commits into one clean commit, removing secrets from
223
+ * intermediate commits (for example, an earlier commit containing .npmrc or .env).
224
224
  */
225
225
  export function squashUnpushedCommits(branch = 'main', commitMsg = 'Clean commit without secrets', dateFormatted = null, cwd = process.cwd()) {
226
226
  const upstream = getUpstreamRef(branch, cwd);
@@ -229,21 +229,21 @@ export function squashUnpushedCommits(branch = 'main', commitMsg = 'Clean commit
229
229
  if (upstream) {
230
230
  const r1 = spawnSync('git', ['reset', '--soft', upstream], { cwd, encoding: 'utf-8' });
231
231
  if (r1.status !== 0) {
232
- throw new Error(String(r1.stderr || r1.stdout || 'Không thể reset về upstream').trim());
232
+ throw new Error(String(r1.stderr || r1.stdout || 'Could not reset to upstream').trim());
233
233
  }
234
234
  } else {
235
- // Chưa có upstream (repo mới hoàn toàn): squash TẤT CẢ thành 1 commit duy nhất
236
- // bằng reset --soft về root rồi --amend root (thay vì commit mới gây ra 2 commits).
235
+ // Without an upstream (a new repository), squash everything into one commit
236
+ // by soft-resetting to the root and amending it instead of creating a second commit.
237
237
  const rRoot = spawnSync('git', ['rev-list', '--max-parents=0', 'HEAD'], { cwd, encoding: 'utf-8' });
238
- if (rRoot.status !== 0) throw new Error('Không đọc được root commit.');
238
+ if (rRoot.status !== 0) throw new Error('Could not read the root commit.');
239
239
  const rootCommit = (rRoot.stdout || '').trim().split(/\s+/)[0];
240
- if (!rootCommit) throw new Error('Không tìm thấy root commit.');
240
+ if (!rootCommit) throw new Error('Root commit not found.');
241
241
  const rReset = spawnSync('git', ['reset', '--soft', rootCommit], { cwd, encoding: 'utf-8' });
242
- if (rReset.status !== 0) throw new Error('Không thể reset về root commit.');
242
+ if (rReset.status !== 0) throw new Error('Could not reset to the root commit.');
243
243
  usedAmendForRoot.value = true;
244
244
  }
245
245
 
246
- // Quét lại và loại bỏ bất kỳ secret file nào còn sót trong staged
246
+ // Scan again and remove any secret files still staged.
247
247
  const risky = findRiskyStagedFiles(cwd);
248
248
  const findings = scanStagedContentForSecrets(cwd);
249
249
  const allSecretFiles = [...new Set([...risky, ...findings.map((f) => f.file)])];
@@ -251,14 +251,14 @@ export function squashUnpushedCommits(branch = 'main', commitMsg = 'Clean commit
251
251
  autoFixStagedSecrets(allSecretFiles, cwd);
252
252
  }
253
253
 
254
- // Đảm bảo .gitignore được stage (nếu tồn tại)
254
+ // Ensure .gitignore is staged, if it exists.
255
255
  spawnSync('git', ['add', '.gitignore'], { cwd });
256
256
 
257
257
  if (!hasStagedChanges(cwd) && !usedAmendForRoot.value) {
258
- return { ok: false, error: 'Không còn thay đổi nào để squash thành commit mới.' };
258
+ return { ok: false, error: 'No changes remain to squash into a new commit.' };
259
259
  }
260
260
 
261
- // Tạo commit mới sạch
261
+ // Create a new clean commit.
262
262
  const env = { ...process.env };
263
263
  if (dateFormatted) {
264
264
  env.GIT_AUTHOR_DATE = dateFormatted;
@@ -267,7 +267,7 @@ export function squashUnpushedCommits(branch = 'main', commitMsg = 'Clean commit
267
267
  const safeMsg = String(commitMsg || 'Clean commit without secrets');
268
268
  let rCommit;
269
269
  if (usedAmendForRoot.value) {
270
- // Amend root để giữ đúng 1 commit duy nhất cho repo chưa có upstream
270
+ // Amend the root to keep a single commit when there is no upstream.
271
271
  rCommit = spawnSync('git', ['commit', '--amend', '-m', safeMsg], { cwd, env, encoding: 'utf-8' });
272
272
  } else {
273
273
  rCommit = spawnSync('git', ['commit', '-m', safeMsg], { cwd, env, encoding: 'utf-8' });
@@ -275,9 +275,9 @@ export function squashUnpushedCommits(branch = 'main', commitMsg = 'Clean commit
275
275
  if (rCommit.status !== 0) {
276
276
  const msg = String(rCommit.stderr || rCommit.stdout || '').trim();
277
277
  if (msg.toLowerCase().includes('nothing to commit')) {
278
- return { ok: false, error: 'Không có gì để commit sau khi làm sạch (working tree sạch).' };
278
+ return { ok: false, error: 'Nothing to commit after cleanup (working tree is clean).' };
279
279
  }
280
- throw new Error(msg || 'Lỗi khi tạo commit squash');
280
+ throw new Error(msg || 'Could not create the squashed commit');
281
281
  }
282
282
  return { ok: true };
283
283
  } catch (err) {