git-push-time 1.1.2 → 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/.github/workflows/npm-publish.yml +59 -32
- package/README.md +110 -112
- package/bin/index.js +321 -339
- package/lib/commands/edit-time.js +141 -141
- package/lib/commands/fix.js +113 -113
- package/lib/commands/github.js +429 -445
- package/lib/commands/pull.js +90 -90
- package/lib/commands/push.js +720 -720
- package/lib/config.js +220 -220
- package/lib/fix.js +286 -286
- package/lib/git.js +525 -516
- package/lib/github.js +322 -322
- package/lib/prompt-helpers.js +19 -20
- package/lib/secrets.js +92 -92
- package/lib/ui.js +1057 -1063
- package/lib/utils.js +120 -120
- package/package.json +36 -36
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
3
|
-
checkGitInstalled,
|
|
4
|
-
isGitRepo,
|
|
5
|
-
getCurrentBranch,
|
|
6
|
-
getRecentCommits,
|
|
7
|
-
rewriteCommitDate,
|
|
8
|
-
resolveTargetDir
|
|
9
|
-
} from '../git.js';
|
|
10
|
-
import { parseCustomDate } from '../utils.js';
|
|
11
|
-
import { selectWithBack, inputWithBack, BACK } from '../prompt-helpers.js';
|
|
12
|
-
import {
|
|
13
|
-
printSection,
|
|
14
|
-
printTargetDir,
|
|
15
|
-
printGitMissing,
|
|
16
|
-
printErrorBox
|
|
17
|
-
} from '../ui.js';
|
|
18
|
-
|
|
19
|
-
export async function runEditCommitTimeCommand(options = {}) {
|
|
20
|
-
const fromMenu = options.fromMenu === true;
|
|
21
|
-
|
|
22
|
-
let targetDir = process.cwd();
|
|
23
|
-
if (options.dir) {
|
|
24
|
-
const r = resolveTargetDir(options.dir, process.cwd());
|
|
25
|
-
if (!r.ok) {
|
|
26
|
-
printErrorBox('
|
|
27
|
-
if (fromMenu) return;
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
targetDir = r.fullPath;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!checkGitInstalled()) {
|
|
34
|
-
printGitMissing();
|
|
35
|
-
if (fromMenu) return;
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
if (!isGitRepo(targetDir)) {
|
|
39
|
-
printErrorBox('
|
|
40
|
-
if (fromMenu) return;
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
printSection('
|
|
45
|
-
printTargetDir(targetDir);
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const commits = getRecentCommits(100, targetDir);
|
|
49
|
-
if (commits.length === 0) {
|
|
50
|
-
printErrorBox('
|
|
51
|
-
if (fromMenu) return;
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
let commit = null;
|
|
57
|
-
let dateInput = null;
|
|
58
|
-
while (true) {
|
|
59
|
-
//
|
|
60
|
-
if (!commit) {
|
|
61
|
-
if (commits.length === 1) {
|
|
62
|
-
commit = commits[0];
|
|
63
|
-
console.log(chalk.hex('#94A3B8')(` »
|
|
64
|
-
} else {
|
|
65
|
-
const picked = await selectWithBack({
|
|
66
|
-
message: `
|
|
67
|
-
pageSize: 12,
|
|
68
|
-
choices: commits.map((item) => ({
|
|
69
|
-
name: `${item.date} ${item.shortHash} ${item.subject}`,
|
|
70
|
-
value: item,
|
|
71
|
-
description: '
|
|
72
|
-
}))
|
|
73
|
-
});
|
|
74
|
-
if (picked === BACK) return;
|
|
75
|
-
commit = picked;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
const dateOption = await selectWithBack({
|
|
81
|
-
message: '
|
|
82
|
-
choices: [
|
|
83
|
-
{ name: '
|
|
84
|
-
{ name: '
|
|
85
|
-
{ name: '
|
|
86
|
-
]
|
|
87
|
-
});
|
|
88
|
-
if (dateOption === BACK) {
|
|
89
|
-
commit = null; //
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
dateInput = dateOption;
|
|
94
|
-
if (dateOption === 'relative') {
|
|
95
|
-
const val = await inputWithBack({
|
|
96
|
-
message: '
|
|
97
|
-
default: '-1d',
|
|
98
|
-
validate: (value) => {
|
|
99
|
-
const parsed = parseCustomDate(value);
|
|
100
|
-
return parsed.valid || parsed.error;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
if (val === BACK) continue; //
|
|
104
|
-
dateInput = val;
|
|
105
|
-
} else if (dateOption === 'custom') {
|
|
106
|
-
const val = await inputWithBack({
|
|
107
|
-
message: '
|
|
108
|
-
validate: (value) => {
|
|
109
|
-
const parsed = parseCustomDate(value);
|
|
110
|
-
return parsed.valid || parsed.error;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
if (val === BACK) continue; //
|
|
114
|
-
dateInput = val;
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const parsed = parseCustomDate(dateInput);
|
|
120
|
-
if (!parsed.valid) {
|
|
121
|
-
printErrorBox('
|
|
122
|
-
if (fromMenu) return;
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const currentBranch = getCurrentBranch(targetDir);
|
|
127
|
-
const result = rewriteCommitDate(commit.hash, parsed.formatted, targetDir);
|
|
128
|
-
|
|
129
|
-
printSection('
|
|
130
|
-
console.log(`
|
|
131
|
-
console.log(` Commit: ${chalk.hex('#FBBF24').bold(commit.shortHash)} — ${commit.subject}`);
|
|
132
|
-
console.log(`
|
|
133
|
-
console.log(`
|
|
134
|
-
console.log(`
|
|
135
|
-
console.log(chalk.hex('#FBBF24')(' [!]
|
|
136
|
-
} catch (error) {
|
|
137
|
-
printErrorBox('
|
|
138
|
-
if (fromMenu) return;
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {
|
|
3
|
+
checkGitInstalled,
|
|
4
|
+
isGitRepo,
|
|
5
|
+
getCurrentBranch,
|
|
6
|
+
getRecentCommits,
|
|
7
|
+
rewriteCommitDate,
|
|
8
|
+
resolveTargetDir
|
|
9
|
+
} from '../git.js';
|
|
10
|
+
import { parseCustomDate } from '../utils.js';
|
|
11
|
+
import { selectWithBack, inputWithBack, BACK } from '../prompt-helpers.js';
|
|
12
|
+
import {
|
|
13
|
+
printSection,
|
|
14
|
+
printTargetDir,
|
|
15
|
+
printGitMissing,
|
|
16
|
+
printErrorBox
|
|
17
|
+
} from '../ui.js';
|
|
18
|
+
|
|
19
|
+
export async function runEditCommitTimeCommand(options = {}) {
|
|
20
|
+
const fromMenu = options.fromMenu === true;
|
|
21
|
+
|
|
22
|
+
let targetDir = process.cwd();
|
|
23
|
+
if (options.dir) {
|
|
24
|
+
const r = resolveTargetDir(options.dir, process.cwd());
|
|
25
|
+
if (!r.ok) {
|
|
26
|
+
printErrorBox('INVALID DIRECTORY', [r.error]);
|
|
27
|
+
if (fromMenu) return;
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
targetDir = r.fullPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!checkGitInstalled()) {
|
|
34
|
+
printGitMissing();
|
|
35
|
+
if (fromMenu) return;
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (!isGitRepo(targetDir)) {
|
|
39
|
+
printErrorBox('NOT A GIT REPOSITORY', [`'${targetDir}' is not a Git repository.`]);
|
|
40
|
+
if (fromMenu) return;
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
printSection('EDIT COMMIT TIMESTAMP');
|
|
45
|
+
printTargetDir(targetDir);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const commits = getRecentCommits(100, targetDir);
|
|
49
|
+
if (commits.length === 0) {
|
|
50
|
+
printErrorBox('NO COMMITS FOUND', ['This repository does not contain any commits.']);
|
|
51
|
+
if (fromMenu) return;
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Use a loop instead of recursion to avoid growing the stack after repeated Back actions.
|
|
56
|
+
let commit = null;
|
|
57
|
+
let dateInput = null;
|
|
58
|
+
while (true) {
|
|
59
|
+
// Step 1: Choose a commit.
|
|
60
|
+
if (!commit) {
|
|
61
|
+
if (commits.length === 1) {
|
|
62
|
+
commit = commits[0];
|
|
63
|
+
console.log(chalk.hex('#94A3B8')(` » Only one commit found; selected it: ${commit.shortHash} — ${commit.subject}\n`));
|
|
64
|
+
} else {
|
|
65
|
+
const picked = await selectWithBack({
|
|
66
|
+
message: `Choose a commit to edit (${commits.length} most recent):`,
|
|
67
|
+
pageSize: 12,
|
|
68
|
+
choices: commits.map((item) => ({
|
|
69
|
+
name: `${item.date} ${item.shortHash} ${item.subject}`,
|
|
70
|
+
value: item,
|
|
71
|
+
description: 'Editing this commit will rewrite it and all following commits'
|
|
72
|
+
}))
|
|
73
|
+
});
|
|
74
|
+
if (picked === BACK) return;
|
|
75
|
+
commit = picked;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Step 2: Choose a timestamp type.
|
|
80
|
+
const dateOption = await selectWithBack({
|
|
81
|
+
message: 'Choose a new timestamp:',
|
|
82
|
+
choices: [
|
|
83
|
+
{ name: 'Now', value: 'now', description: 'Use the current time' },
|
|
84
|
+
{ name: 'Enter a relative time', value: 'relative', description: 'e.g. -2d, -5h, -30m, -3M' },
|
|
85
|
+
{ name: 'Enter a specific date and time', value: 'custom', description: 'e.g. 2024-01-15 14:30:00' }
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
if (dateOption === BACK) {
|
|
89
|
+
commit = null; // Return to commit selection.
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
dateInput = dateOption;
|
|
94
|
+
if (dateOption === 'relative') {
|
|
95
|
+
const val = await inputWithBack({
|
|
96
|
+
message: 'Enter a relative time (e.g. -2d, -5h, -30m, -3M):',
|
|
97
|
+
default: '-1d',
|
|
98
|
+
validate: (value) => {
|
|
99
|
+
const parsed = parseCustomDate(value);
|
|
100
|
+
return parsed.valid || parsed.error;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (val === BACK) continue; // Return to timestamp type selection.
|
|
104
|
+
dateInput = val;
|
|
105
|
+
} else if (dateOption === 'custom') {
|
|
106
|
+
const val = await inputWithBack({
|
|
107
|
+
message: 'Enter a date and time (YYYY-MM-DD HH:mm:ss):',
|
|
108
|
+
validate: (value) => {
|
|
109
|
+
const parsed = parseCustomDate(value);
|
|
110
|
+
return parsed.valid || parsed.error;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (val === BACK) continue; // Return to timestamp type selection.
|
|
114
|
+
dateInput = val;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parsed = parseCustomDate(dateInput);
|
|
120
|
+
if (!parsed.valid) {
|
|
121
|
+
printErrorBox('INVALID DATE OR TIME', [parsed.error], 'Examples: now, -2d, -5h, 2024-01-15 14:30:00');
|
|
122
|
+
if (fromMenu) return;
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const currentBranch = getCurrentBranch(targetDir);
|
|
127
|
+
const result = rewriteCommitDate(commit.hash, parsed.formatted, targetDir);
|
|
128
|
+
|
|
129
|
+
printSection('COMMIT TIMESTAMP UPDATED');
|
|
130
|
+
console.log(` Branch: ${chalk.hex('#38BDF8').bold(currentBranch)}`);
|
|
131
|
+
console.log(` Commit: ${chalk.hex('#FBBF24').bold(commit.shortHash)} — ${commit.subject}`);
|
|
132
|
+
console.log(` New timestamp: ${chalk.hex('#C084FC').bold(parsed.display)}`);
|
|
133
|
+
console.log(` Rewrote ${result.rewrittenCount} commit(s) on the current branch.`);
|
|
134
|
+
console.log(` Backup branch: ${chalk.hex('#6EE7B7')(result.backupBranch)}`);
|
|
135
|
+
console.log(chalk.hex('#FBBF24')(' [!] If these commits were already pushed, force-push (--force) to update the remote.\n'));
|
|
136
|
+
} catch (error) {
|
|
137
|
+
printErrorBox('COULD NOT EDIT TIMESTAMP', [error.message], 'The working tree must be clean, and the commit must be on the current branch.');
|
|
138
|
+
if (fromMenu) return;
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
package/lib/commands/fix.js
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import {
|
|
4
|
-
checkGitInstalled,
|
|
5
|
-
isGitRepo,
|
|
6
|
-
getCurrentBranchStrict,
|
|
7
|
-
findRiskyStagedFiles,
|
|
8
|
-
resolveTargetDir
|
|
9
|
-
} from '../git.js';
|
|
10
|
-
import { scanStagedContentForSecrets } from '../secrets.js';
|
|
11
|
-
import {
|
|
12
|
-
autoFixStagedSecrets,
|
|
13
|
-
findRiskyWorkingTreeFiles,
|
|
14
|
-
getUnpushedCommits,
|
|
15
|
-
squashUnpushedCommits
|
|
16
|
-
} from '../fix.js';
|
|
17
|
-
import { selectWithBack, BACK } from '../prompt-helpers.js';
|
|
18
|
-
import {
|
|
19
|
-
printSection,
|
|
20
|
-
printTargetDir,
|
|
21
|
-
printGitMissing,
|
|
22
|
-
printErrorBox,
|
|
23
|
-
printPrePushSecretWarning,
|
|
24
|
-
printFixResult
|
|
25
|
-
} from '../ui.js';
|
|
26
|
-
|
|
27
|
-
export async function runFixCommand(options = {}) {
|
|
28
|
-
const fromMenu = options.fromMenu === true;
|
|
29
|
-
|
|
30
|
-
if (!checkGitInstalled()) {
|
|
31
|
-
printGitMissing();
|
|
32
|
-
if (fromMenu) return;
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let targetDir = process.cwd();
|
|
37
|
-
if (options.dir) {
|
|
38
|
-
const r = resolveTargetDir(options.dir, process.cwd());
|
|
39
|
-
if (!r.ok) {
|
|
40
|
-
printErrorBox('
|
|
41
|
-
if (fromMenu) return;
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
targetDir = r.fullPath;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
printSection('
|
|
48
|
-
printTargetDir(targetDir);
|
|
49
|
-
|
|
50
|
-
if (!isGitRepo(targetDir)) {
|
|
51
|
-
printErrorBox('
|
|
52
|
-
if (fromMenu) return;
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const spinner = ora({ text: '
|
|
57
|
-
const risky = findRiskyStagedFiles(targetDir);
|
|
58
|
-
const findings = scanStagedContentForSecrets(targetDir);
|
|
59
|
-
const riskyWorking = findRiskyWorkingTreeFiles(targetDir);
|
|
60
|
-
spinner.stop();
|
|
61
|
-
|
|
62
|
-
const allDetected = [...new Set([...risky, ...findings.map((f) => f.file), ...riskyWorking])];
|
|
63
|
-
const hadSecrets = allDetected.length > 0;
|
|
64
|
-
|
|
65
|
-
if (allDetected.length === 0) {
|
|
66
|
-
console.log(chalk.hex('#34D399')(' [OK]
|
|
67
|
-
} else {
|
|
68
|
-
printPrePushSecretWarning({ riskyFiles: allDetected, findings });
|
|
69
|
-
spinner.start('
|
|
70
|
-
const res = autoFixStagedSecrets(allDetected, targetDir);
|
|
71
|
-
spinner.succeed(chalk.hex('#34D399')(`[OK]
|
|
72
|
-
printFixResult(res);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
const currentBranch = getCurrentBranchStrict(targetDir) || 'main';
|
|
78
|
-
const unpushed = getUnpushedCommits(currentBranch, targetDir);
|
|
79
|
-
if (unpushed.length > 0 && hadSecrets) {
|
|
80
|
-
printSection(`${unpushed.length}
|
|
81
|
-
unpushed.slice(0, 5).forEach((c) => console.log(chalk.hex('#94A3B8')(` - ${c}`)));
|
|
82
|
-
if (unpushed.length > 5) {
|
|
83
|
-
console.log(chalk.hex('#64748B')(` ...
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let sqChoice = 'no';
|
|
87
|
-
if (options.yes) {
|
|
88
|
-
sqChoice = 'no';
|
|
89
|
-
} else {
|
|
90
|
-
sqChoice = await selectWithBack({
|
|
91
|
-
message: `
|
|
92
|
-
choices: [
|
|
93
|
-
{ name: '
|
|
94
|
-
{ name: '
|
|
95
|
-
]
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (sqChoice !== BACK && sqChoice === 'yes') {
|
|
100
|
-
spinner.start('
|
|
101
|
-
const sq = squashUnpushedCommits(currentBranch, 'Clean commit without secrets', null, targetDir);
|
|
102
|
-
if (sq.ok) {
|
|
103
|
-
spinner.succeed(chalk.hex('#34D399')('[OK]
|
|
104
|
-
} else {
|
|
105
|
-
spinner.fail(chalk.red(`
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} else if (unpushed.length > 0) {
|
|
109
|
-
console.log(chalk.hex('#64748B')(` »
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log(chalk.hex('#34D399')(`\n [OK]
|
|
113
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import {
|
|
4
|
+
checkGitInstalled,
|
|
5
|
+
isGitRepo,
|
|
6
|
+
getCurrentBranchStrict,
|
|
7
|
+
findRiskyStagedFiles,
|
|
8
|
+
resolveTargetDir
|
|
9
|
+
} from '../git.js';
|
|
10
|
+
import { scanStagedContentForSecrets } from '../secrets.js';
|
|
11
|
+
import {
|
|
12
|
+
autoFixStagedSecrets,
|
|
13
|
+
findRiskyWorkingTreeFiles,
|
|
14
|
+
getUnpushedCommits,
|
|
15
|
+
squashUnpushedCommits
|
|
16
|
+
} from '../fix.js';
|
|
17
|
+
import { selectWithBack, BACK } from '../prompt-helpers.js';
|
|
18
|
+
import {
|
|
19
|
+
printSection,
|
|
20
|
+
printTargetDir,
|
|
21
|
+
printGitMissing,
|
|
22
|
+
printErrorBox,
|
|
23
|
+
printPrePushSecretWarning,
|
|
24
|
+
printFixResult
|
|
25
|
+
} from '../ui.js';
|
|
26
|
+
|
|
27
|
+
export async function runFixCommand(options = {}) {
|
|
28
|
+
const fromMenu = options.fromMenu === true;
|
|
29
|
+
|
|
30
|
+
if (!checkGitInstalled()) {
|
|
31
|
+
printGitMissing();
|
|
32
|
+
if (fromMenu) return;
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let targetDir = process.cwd();
|
|
37
|
+
if (options.dir) {
|
|
38
|
+
const r = resolveTargetDir(options.dir, process.cwd());
|
|
39
|
+
if (!r.ok) {
|
|
40
|
+
printErrorBox('INVALID DIRECTORY', [r.error]);
|
|
41
|
+
if (fromMenu) return;
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
targetDir = r.fullPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
printSection('SCAN FOR SECRETS');
|
|
48
|
+
printTargetDir(targetDir);
|
|
49
|
+
|
|
50
|
+
if (!isGitRepo(targetDir)) {
|
|
51
|
+
printErrorBox('NOT A GIT REPOSITORY', [`'${targetDir}' is not a Git repository.`]);
|
|
52
|
+
if (fromMenu) return;
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const spinner = ora({ text: 'Checking staged files and the working tree…', color: 'cyan' }).start();
|
|
57
|
+
const risky = findRiskyStagedFiles(targetDir);
|
|
58
|
+
const findings = scanStagedContentForSecrets(targetDir);
|
|
59
|
+
const riskyWorking = findRiskyWorkingTreeFiles(targetDir);
|
|
60
|
+
spinner.stop();
|
|
61
|
+
|
|
62
|
+
const allDetected = [...new Set([...risky, ...findings.map((f) => f.file), ...riskyWorking])];
|
|
63
|
+
const hadSecrets = allDetected.length > 0;
|
|
64
|
+
|
|
65
|
+
if (allDetected.length === 0) {
|
|
66
|
+
console.log(chalk.hex('#34D399')(' [OK] No secrets found in staged files or the working tree.\n'));
|
|
67
|
+
} else {
|
|
68
|
+
printPrePushSecretWarning({ riskyFiles: allDetected, findings });
|
|
69
|
+
spinner.start('Applying fixes (unstaging files and updating .gitignore)...');
|
|
70
|
+
const res = autoFixStagedSecrets(allDetected, targetDir);
|
|
71
|
+
spinner.succeed(chalk.hex('#34D399')(`[OK] Processed ${res.fixed.length} file(s).`));
|
|
72
|
+
printFixResult(res);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Suggest squashing unpushed commits only when secrets were detected,
|
|
76
|
+
// avoiding unnecessary rewrites of clean history.
|
|
77
|
+
const currentBranch = getCurrentBranchStrict(targetDir) || 'main';
|
|
78
|
+
const unpushed = getUnpushedCommits(currentBranch, targetDir);
|
|
79
|
+
if (unpushed.length > 0 && hadSecrets) {
|
|
80
|
+
printSection(`${unpushed.length} UNPUSHED LOCAL COMMITS`);
|
|
81
|
+
unpushed.slice(0, 5).forEach((c) => console.log(chalk.hex('#94A3B8')(` - ${c}`)));
|
|
82
|
+
if (unpushed.length > 5) {
|
|
83
|
+
console.log(chalk.hex('#64748B')(` ...and ${unpushed.length - 5} more commit(s)`));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let sqChoice = 'no';
|
|
87
|
+
if (options.yes) {
|
|
88
|
+
sqChoice = 'no';
|
|
89
|
+
} else {
|
|
90
|
+
sqChoice = await selectWithBack({
|
|
91
|
+
message: `Squash ${unpushed.length} commit(s) into one clean commit to remove old secrets from the history?`,
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Yes, create one clean commit', value: 'yes', description: 'Clean the commit history' },
|
|
94
|
+
{ name: 'No, keep as is', value: 'no', description: 'Skip' }
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (sqChoice !== BACK && sqChoice === 'yes') {
|
|
100
|
+
spinner.start('Cleaning unpushed commit history...');
|
|
101
|
+
const sq = squashUnpushedCommits(currentBranch, 'Clean commit without secrets', null, targetDir);
|
|
102
|
+
if (sq.ok) {
|
|
103
|
+
spinner.succeed(chalk.hex('#34D399')('[OK] Unpushed commits cleaned. Ready to push safely.'));
|
|
104
|
+
} else {
|
|
105
|
+
spinner.fail(chalk.red(`Could not squash commits: ${sq.error}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else if (unpushed.length > 0) {
|
|
109
|
+
console.log(chalk.hex('#64748B')(` » ${unpushed.length} unpushed commit(s) found; history is clean, so no squash is needed.\n`));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(chalk.hex('#34D399')(`\n [OK] Scan complete on branch ${currentBranch}. Repository is safe.\n`));
|
|
113
|
+
}
|