gittable 1.0.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/LICENSE +22 -0
- package/README.md +459 -0
- package/cli.js +342 -0
- package/commands/add.js +159 -0
- package/commands/blame.js +33 -0
- package/commands/branch.js +234 -0
- package/commands/checkout.js +43 -0
- package/commands/cherry-pick.js +104 -0
- package/commands/clean.js +71 -0
- package/commands/clone.js +76 -0
- package/commands/commit.js +82 -0
- package/commands/config.js +171 -0
- package/commands/diff.js +30 -0
- package/commands/fetch.js +76 -0
- package/commands/grep.js +42 -0
- package/commands/init.js +45 -0
- package/commands/log.js +38 -0
- package/commands/merge.js +69 -0
- package/commands/mv.js +40 -0
- package/commands/pull.js +74 -0
- package/commands/push.js +97 -0
- package/commands/rebase.js +134 -0
- package/commands/remote.js +236 -0
- package/commands/restore.js +76 -0
- package/commands/revert.js +63 -0
- package/commands/rm.js +57 -0
- package/commands/show.js +47 -0
- package/commands/stash.js +201 -0
- package/commands/status.js +21 -0
- package/commands/sync.js +98 -0
- package/commands/tag.js +153 -0
- package/commands/undo.js +200 -0
- package/commands/uninit.js +57 -0
- package/index.d.ts +56 -0
- package/index.js +55 -0
- package/lib/commit/build-commit.js +64 -0
- package/lib/commit/get-previous-commit.js +15 -0
- package/lib/commit/questions.js +226 -0
- package/lib/config/read-config-file.js +54 -0
- package/lib/git/exec.js +222 -0
- package/lib/ui/ascii.js +154 -0
- package/lib/ui/banner.js +80 -0
- package/lib/ui/status-display.js +90 -0
- package/lib/ui/table.js +76 -0
- package/lib/utils/email-prompt.js +62 -0
- package/lib/utils/logger.js +47 -0
- package/lib/utils/spinner.js +57 -0
- package/lib/utils/terminal-link.js +55 -0
- package/lib/versions.js +17 -0
- package/package.json +73 -0
- package/standalone.js +24 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const clack = require('@clack/prompts');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { isGitRepo } = require('../lib/git/exec');
|
|
4
|
+
const { showBanner } = require('../lib/ui/banner');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
module.exports = async (args) => {
|
|
9
|
+
showBanner('UNINIT');
|
|
10
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Remove Git Repository')}`);
|
|
11
|
+
|
|
12
|
+
if (!isGitRepo()) {
|
|
13
|
+
clack.cancel(chalk.yellow('Not a git repository'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
18
|
+
const gitDir = path.join(process.cwd(), '.git');
|
|
19
|
+
|
|
20
|
+
if (!force) {
|
|
21
|
+
const confirm = await clack.confirm({
|
|
22
|
+
message: chalk.red('This will permanently delete all git history. Continue?'),
|
|
23
|
+
initialValue: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (clack.isCancel(confirm) || !confirm) {
|
|
27
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const spinner = clack.spinner();
|
|
33
|
+
spinner.start('Removing git repository');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Check if .git is a file (submodule) or directory
|
|
37
|
+
const gitStat = fs.statSync(gitDir);
|
|
38
|
+
|
|
39
|
+
if (gitStat.isFile()) {
|
|
40
|
+
// It's a submodule - just remove the file
|
|
41
|
+
fs.unlinkSync(gitDir);
|
|
42
|
+
} else if (gitStat.isDirectory()) {
|
|
43
|
+
// Remove the entire .git directory
|
|
44
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
spinner.stop();
|
|
48
|
+
clack.outro(
|
|
49
|
+
chalk.green.bold('Git repository removed. You can now run "gittable init" for a fresh start.')
|
|
50
|
+
);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
spinner.stop();
|
|
53
|
+
clack.cancel(chalk.red('Failed to remove git repository'));
|
|
54
|
+
console.error(error.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
declare module 'gittable' {
|
|
2
|
+
export interface CommitType {
|
|
3
|
+
value: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Scope {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Config {
|
|
12
|
+
types: CommitType[];
|
|
13
|
+
scopes?: Scope[];
|
|
14
|
+
scopeOverrides?: Record<string, Scope[]>;
|
|
15
|
+
|
|
16
|
+
allowTicketNumber?: boolean;
|
|
17
|
+
isTicketNumberRequired?: boolean;
|
|
18
|
+
ticketNumberPrefix?: string;
|
|
19
|
+
ticketNumberSuffix?: string;
|
|
20
|
+
ticketNumberRegExp?: string;
|
|
21
|
+
fallbackTicketNumber?: string;
|
|
22
|
+
prependTicketToHead?: boolean;
|
|
23
|
+
|
|
24
|
+
allowCustomScopes?: boolean;
|
|
25
|
+
allowBreakingChanges?: string[];
|
|
26
|
+
skipQuestions?: string[];
|
|
27
|
+
skipEmptyScopes?: boolean;
|
|
28
|
+
|
|
29
|
+
subjectLimit?: number;
|
|
30
|
+
subjectSeparator?: string;
|
|
31
|
+
typePrefix?: string;
|
|
32
|
+
typeSuffix?: string;
|
|
33
|
+
upperCaseSubject?: boolean;
|
|
34
|
+
|
|
35
|
+
breaklineChar?: string;
|
|
36
|
+
breakingPrefix?: string;
|
|
37
|
+
footerPrefix?: string;
|
|
38
|
+
|
|
39
|
+
usePreparedCommit?: boolean;
|
|
40
|
+
askForBreakingChangeFirst?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Answers {
|
|
44
|
+
type: string;
|
|
45
|
+
scope: string | null;
|
|
46
|
+
ticketNumber?: string;
|
|
47
|
+
subject: string;
|
|
48
|
+
body?: string;
|
|
49
|
+
breaking?: string;
|
|
50
|
+
footer?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type Prompter = (cz: unknown, commit: (message: string) => void) => Promise<void>;
|
|
54
|
+
|
|
55
|
+
export const prompter: Prompter;
|
|
56
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const clack = require('@clack/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { promptQuestions } = require('./lib/commit/questions');
|
|
6
|
+
const buildCommit = require('./lib/commit/build-commit');
|
|
7
|
+
const readConfigFile = require('./lib/config/read-config-file');
|
|
8
|
+
|
|
9
|
+
const prompter = async (_, commit) => {
|
|
10
|
+
const config = readConfigFile();
|
|
11
|
+
if (!config) process.exit(1);
|
|
12
|
+
|
|
13
|
+
config.subjectLimit = config.subjectLimit || 100;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const answers = await promptQuestions(config);
|
|
17
|
+
const message = buildCommit(answers, config);
|
|
18
|
+
|
|
19
|
+
clack.note(message, chalk.bold('Commit Preview'));
|
|
20
|
+
|
|
21
|
+
const action = await clack.select({
|
|
22
|
+
message: chalk.yellow('Proceed?'),
|
|
23
|
+
options: [
|
|
24
|
+
{ value: 'yes', label: chalk.green('Commit') },
|
|
25
|
+
{ value: 'no', label: chalk.red('Cancel') },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (clack.isCancel(action) || action === 'no') {
|
|
30
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const spinner = clack.spinner();
|
|
35
|
+
spinner.start('Creating commit');
|
|
36
|
+
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
try {
|
|
39
|
+
commit(message);
|
|
40
|
+
resolve();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
reject(err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
spinner.stop('Commit created');
|
|
47
|
+
clack.outro(chalk.green.bold('Success'));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
clack.cancel(chalk.red('Failed'));
|
|
50
|
+
console.error(error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
module.exports = { prompter };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const wrap = require('word-wrap');
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
separator: ': ',
|
|
5
|
+
maxWidth: 100,
|
|
6
|
+
breakChar: '|',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const wrapText = (text, width = DEFAULTS.maxWidth) =>
|
|
10
|
+
text ? wrap(text, { trim: true, newline: '\n', indent: '', width }) : '';
|
|
11
|
+
|
|
12
|
+
const escapeChars = (str) => str.replace(/[`"\\$!<>&]/g, (char) => `\\${char}`);
|
|
13
|
+
|
|
14
|
+
const addBreaklines = (text, char = DEFAULTS.breakChar) => text.split(char).join('\n');
|
|
15
|
+
|
|
16
|
+
const buildCommit = (answers, config) => {
|
|
17
|
+
const parts = [];
|
|
18
|
+
|
|
19
|
+
// Build header: type(scope): subject
|
|
20
|
+
let header = config.typePrefix || '';
|
|
21
|
+
header += answers.type;
|
|
22
|
+
header += config.typeSuffix || '';
|
|
23
|
+
|
|
24
|
+
if (answers.scope) {
|
|
25
|
+
header += `(${answers.scope})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
header += config.subjectSeparator || DEFAULTS.separator;
|
|
29
|
+
|
|
30
|
+
if (config.prependTicketToHead && answers.ticketNumber) {
|
|
31
|
+
const ticket = answers.ticketNumber.trim();
|
|
32
|
+
const prefix = config.ticketNumberPrefix || '';
|
|
33
|
+
const suffix = config.ticketNumberSuffix || '';
|
|
34
|
+
header = `${prefix}${ticket}${suffix} ${header}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
header += answers.subject.slice(0, config.subjectLimit || 100);
|
|
38
|
+
|
|
39
|
+
parts.push(header);
|
|
40
|
+
|
|
41
|
+
// Add body
|
|
42
|
+
if (answers.body) {
|
|
43
|
+
const body = wrapText(addBreaklines(answers.body, config.breaklineChar));
|
|
44
|
+
parts.push('', body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Add breaking changes
|
|
48
|
+
if (answers.breaking) {
|
|
49
|
+
const prefix = config.breakingPrefix || 'BREAKING CHANGE:';
|
|
50
|
+
const breaking = wrapText(answers.breaking);
|
|
51
|
+
parts.push('', `${prefix}\n${breaking}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add footer
|
|
55
|
+
if (answers.footer) {
|
|
56
|
+
const prefix = config.footerPrefix === '' ? '' : config.footerPrefix || 'ISSUES CLOSED:';
|
|
57
|
+
const footer = wrapText(addBreaklines(answers.footer, config.breaklineChar));
|
|
58
|
+
parts.push('', prefix ? `${prefix} ${footer}` : footer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return escapeChars(parts.join('\n'));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
module.exports = buildCommit;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
|
|
3
|
+
const getPreviousCommit = () => {
|
|
4
|
+
const path = './.git/COMMIT_EDITMSG';
|
|
5
|
+
if (!fs.existsSync(path)) return null;
|
|
6
|
+
|
|
7
|
+
return fs
|
|
8
|
+
.readFileSync(path, 'utf-8')
|
|
9
|
+
.replace(/^#.*/gm, '')
|
|
10
|
+
.replace(/^\s*[\r\n]/gm, '')
|
|
11
|
+
.replace(/[\r\n]$/, '')
|
|
12
|
+
.split(/\r\n|\r|\n/);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = getPreviousCommit;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const clack = require('@clack/prompts');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const _fs = require('node:fs');
|
|
4
|
+
const getPreviousCommit = require('./get-previous-commit');
|
|
5
|
+
|
|
6
|
+
// Constants
|
|
7
|
+
const SCOPE_CATEGORIES = {
|
|
8
|
+
'Core App Structure': ['app', 'routing', 'layout', 'middleware'],
|
|
9
|
+
|
|
10
|
+
'Rendering & Runtime': ['rsc', 'client', 'server'],
|
|
11
|
+
|
|
12
|
+
'UI & Shared Logic': ['components', 'styles', 'hooks', 'utils', 'types'],
|
|
13
|
+
|
|
14
|
+
'Data & API': ['api', 'auth', 'db', 'cache'],
|
|
15
|
+
|
|
16
|
+
'Dev & Tooling': ['config', 'logging', 'test'],
|
|
17
|
+
|
|
18
|
+
Infrastructure: ['infra', 'env'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const categorizeScopesOptimized = (scopes) => {
|
|
22
|
+
const result = {};
|
|
23
|
+
const uncategorized = [];
|
|
24
|
+
|
|
25
|
+
// Initialize categories
|
|
26
|
+
for (const cat of Object.keys(SCOPE_CATEGORIES)) {
|
|
27
|
+
result[cat] = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Build lookup map for O(1) categorization
|
|
31
|
+
const lookup = new Map();
|
|
32
|
+
for (const [cat, scopeNames] of Object.entries(SCOPE_CATEGORIES)) {
|
|
33
|
+
for (const name of scopeNames) {
|
|
34
|
+
lookup.set(name, cat);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Categorize scopes
|
|
39
|
+
for (const scope of scopes) {
|
|
40
|
+
const name = typeof scope === 'string' ? scope : scope.name;
|
|
41
|
+
const category = lookup.get(name);
|
|
42
|
+
|
|
43
|
+
if (category) {
|
|
44
|
+
result[category].push(scope);
|
|
45
|
+
} else {
|
|
46
|
+
uncategorized.push(scope);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (uncategorized.length > 0) {
|
|
51
|
+
result.Other = uncategorized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Remove empty categories
|
|
55
|
+
return Object.fromEntries(Object.entries(result).filter(([_, scopes]) => scopes.length > 0));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleCancel = () => {
|
|
59
|
+
clack.cancel(chalk.yellow('Operation cancelled'));
|
|
60
|
+
process.exit(0);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createValidator = (config, field) => {
|
|
64
|
+
const validators = {
|
|
65
|
+
ticket: (value) => {
|
|
66
|
+
if (!value && config.isTicketNumberRequired && !config.fallbackTicketNumber) {
|
|
67
|
+
return 'Ticket number is required';
|
|
68
|
+
}
|
|
69
|
+
if (value && config.ticketNumberRegExp) {
|
|
70
|
+
const regex = new RegExp(config.ticketNumberRegExp);
|
|
71
|
+
if (!regex.test(value)) {
|
|
72
|
+
return `Must match pattern: ${config.ticketNumberRegExp}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
subject: (value) => {
|
|
77
|
+
if (!value) return 'Subject is required';
|
|
78
|
+
const limit = config.subjectLimit || 100;
|
|
79
|
+
if (value.length > limit) {
|
|
80
|
+
return chalk.red(`Exceeds ${limit} chars (current: ${value.length})`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return validators[field];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Main prompt function
|
|
89
|
+
async function promptQuestions(config) {
|
|
90
|
+
// Optimize scope handling
|
|
91
|
+
const scopes = config.scopeOverrides || config.scopes || [];
|
|
92
|
+
const categorizedScopes = scopes.length ? categorizeScopesOptimized(scopes) : null;
|
|
93
|
+
|
|
94
|
+
// Use clack.group for better performance and UX
|
|
95
|
+
const answers = await clack.group(
|
|
96
|
+
{
|
|
97
|
+
type: () =>
|
|
98
|
+
clack.select({
|
|
99
|
+
message: chalk.cyan('Select commit type:'),
|
|
100
|
+
options: config.types.map((t) => ({
|
|
101
|
+
value: t.value,
|
|
102
|
+
label: t.name,
|
|
103
|
+
hint: t.value,
|
|
104
|
+
})),
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
scopeCategory: ({ results }) => {
|
|
108
|
+
if (!categorizedScopes || results.type === 'wip') return Promise.resolve(null);
|
|
109
|
+
|
|
110
|
+
const categories = Object.keys(categorizedScopes);
|
|
111
|
+
const options = categories.map((cat) => ({
|
|
112
|
+
value: cat,
|
|
113
|
+
label: cat,
|
|
114
|
+
hint: `${categorizedScopes[cat].length} scopes`,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
options.push(
|
|
118
|
+
{ value: '__empty__', label: chalk.dim('No scope') },
|
|
119
|
+
{ value: '__custom__', label: chalk.dim('Custom scope') }
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return clack.select({
|
|
123
|
+
message: chalk.cyan('Select scope category:'),
|
|
124
|
+
options,
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
scope: ({ results }) => {
|
|
129
|
+
if (!results.scopeCategory || results.scopeCategory === '__empty__') {
|
|
130
|
+
return Promise.resolve(null);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (results.scopeCategory === '__custom__') {
|
|
134
|
+
return clack.text({
|
|
135
|
+
message: chalk.cyan('Enter custom scope:'),
|
|
136
|
+
placeholder: 'e.g., auth, api, ui',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const categoryScopes = categorizedScopes[results.scopeCategory];
|
|
141
|
+
return clack.select({
|
|
142
|
+
message: chalk.cyan('Select scope:'),
|
|
143
|
+
options: categoryScopes.map((s) => {
|
|
144
|
+
const name = typeof s === 'string' ? s : s.name;
|
|
145
|
+
return { value: name, label: name };
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
ticketNumber: () => {
|
|
151
|
+
if (!config.allowTicketNumber) return Promise.resolve('');
|
|
152
|
+
|
|
153
|
+
return clack.text({
|
|
154
|
+
message: chalk.cyan('Ticket number:'),
|
|
155
|
+
placeholder: config.ticketNumberPrefix || 'TICKET-',
|
|
156
|
+
defaultValue: config.fallbackTicketNumber || '',
|
|
157
|
+
validate: createValidator(config, 'ticket'),
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
subject: () => {
|
|
162
|
+
const previous = getPreviousCommit();
|
|
163
|
+
const defaultValue = (config.usePreparedCommit && previous?.[0]) || '';
|
|
164
|
+
|
|
165
|
+
return clack.text({
|
|
166
|
+
message: chalk.cyan('Commit message:'),
|
|
167
|
+
placeholder: 'add user authentication',
|
|
168
|
+
defaultValue,
|
|
169
|
+
validate: createValidator(config, 'subject'),
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
body: () => {
|
|
174
|
+
if (config.skipQuestions?.includes('body')) return Promise.resolve('');
|
|
175
|
+
|
|
176
|
+
const previous = getPreviousCommit();
|
|
177
|
+
const defaultValue =
|
|
178
|
+
config.usePreparedCommit && previous?.length > 1 ? previous.slice(1).join('|') : '';
|
|
179
|
+
|
|
180
|
+
return clack.text({
|
|
181
|
+
message: chalk.cyan('Extended description (optional):'),
|
|
182
|
+
placeholder: 'Use "|" for new lines',
|
|
183
|
+
defaultValue,
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
breaking: ({ results }) => {
|
|
188
|
+
const shouldAsk =
|
|
189
|
+
config.askForBreakingChangeFirst || config.allowBreakingChanges?.includes(results.type);
|
|
190
|
+
|
|
191
|
+
if (!shouldAsk) return Promise.resolve('');
|
|
192
|
+
|
|
193
|
+
return clack.text({
|
|
194
|
+
message: chalk.red('Breaking changes (optional):'),
|
|
195
|
+
placeholder: 'Describe breaking changes',
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
footer: ({ results }) => {
|
|
200
|
+
if (results.type === 'wip' || config.skipQuestions?.includes('footer')) {
|
|
201
|
+
return Promise.resolve('');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return clack.text({
|
|
205
|
+
message: chalk.cyan('Issues closed (optional):'),
|
|
206
|
+
placeholder: '#31, #34',
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
onCancel: handleCancel,
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Process subject casing
|
|
216
|
+
if (answers.subject) {
|
|
217
|
+
const shouldUpperCase = config.upperCaseSubject || false;
|
|
218
|
+
answers.subject = shouldUpperCase
|
|
219
|
+
? answers.subject.charAt(0).toUpperCase() + answers.subject.slice(1)
|
|
220
|
+
: answers.subject.charAt(0).toLowerCase() + answers.subject.slice(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return answers;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = { promptQuestions };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const findConfig = require('find-config');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const log = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
const CONFIG_NAMES = ['.gittable.js', '.gittable.json'];
|
|
7
|
+
|
|
8
|
+
const showError = () => {
|
|
9
|
+
console.log();
|
|
10
|
+
log.error(chalk.red.bold('No configuration found!'));
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(chalk.yellow(' Create one of:'));
|
|
13
|
+
for (const name of CONFIG_NAMES) {
|
|
14
|
+
console.log(chalk.cyan(` • ${name}`));
|
|
15
|
+
}
|
|
16
|
+
console.log(chalk.cyan(' • package.json config'));
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(chalk.gray(' Docs: github.com/leonardoanalista/cz-customizable'));
|
|
19
|
+
console.log();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const readConfigFile = () => {
|
|
23
|
+
// Try .cz-config.js
|
|
24
|
+
const jsConfig = findConfig.require(CONFIG_NAMES[0], { home: false });
|
|
25
|
+
if (jsConfig) {
|
|
26
|
+
log.success(chalk.green(`Using: ${chalk.bold(CONFIG_NAMES[0])}`));
|
|
27
|
+
return jsConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try .cz-config.json
|
|
31
|
+
const jsonConfig = findConfig.require(CONFIG_NAMES[1], { home: false });
|
|
32
|
+
if (jsonConfig) {
|
|
33
|
+
log.success(chalk.green(`Using: ${chalk.bold(CONFIG_NAMES[1])}`));
|
|
34
|
+
return jsonConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Try package.json
|
|
38
|
+
const pkgPath = findConfig('package.json', { home: false });
|
|
39
|
+
if (pkgPath) {
|
|
40
|
+
const pkg = require(pkgPath);
|
|
41
|
+
const configPath = pkg.config?.['cz-customizable']?.config;
|
|
42
|
+
|
|
43
|
+
if (configPath) {
|
|
44
|
+
const fullPath = path.resolve(path.dirname(pkgPath), configPath);
|
|
45
|
+
log.success(chalk.green(`Using: ${chalk.bold(fullPath)}`));
|
|
46
|
+
return require(fullPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
showError();
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = readConfigFile;
|