gh-setup-git-identity 0.2.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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +306 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +24 -0
- package/README.md +246 -0
- package/package.json +46 -0
- package/scripts/changeset-version.mjs +38 -0
- package/scripts/create-github-release.mjs +92 -0
- package/scripts/create-manual-changeset.mjs +80 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +209 -0
- package/scripts/instant-version-bump.mjs +121 -0
- package/scripts/publish-to-npm.mjs +122 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +99 -0
- package/scripts/version-and-commit.mjs +237 -0
- package/src/cli.js +130 -0
- package/src/index.js +312 -0
- package/test/index.test.js +114 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gh-setup-git-identity - Core library for setting up git identity
|
|
5
|
+
*
|
|
6
|
+
* This library provides functionality to:
|
|
7
|
+
* - Check if GitHub CLI is authenticated
|
|
8
|
+
* - Get GitHub user information (username and email)
|
|
9
|
+
* - Configure git user.name and user.email
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import makeLog from 'log-lazy';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a logger instance
|
|
17
|
+
* This can be customized by users when using the library
|
|
18
|
+
*/
|
|
19
|
+
function createDefaultLogger(options = {}) {
|
|
20
|
+
const { verbose = false, logger = console } = options;
|
|
21
|
+
|
|
22
|
+
const log = makeLog({
|
|
23
|
+
level: verbose ? 'development' : 'info',
|
|
24
|
+
log: {
|
|
25
|
+
fatal: logger.error || logger.log,
|
|
26
|
+
error: logger.error || logger.log,
|
|
27
|
+
warn: logger.warn || logger.log,
|
|
28
|
+
info: logger.log,
|
|
29
|
+
debug: logger.debug || logger.log,
|
|
30
|
+
verbose: logger.log,
|
|
31
|
+
trace: logger.log,
|
|
32
|
+
silly: logger.log
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return log;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute a command and return the result
|
|
41
|
+
*
|
|
42
|
+
* @param {string} command - The command to execute
|
|
43
|
+
* @param {string[]} args - The command arguments
|
|
44
|
+
* @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
|
|
45
|
+
*/
|
|
46
|
+
function execCommand(command, args = []) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const child = spawn(command, args, { stdio: 'pipe', shell: false });
|
|
49
|
+
|
|
50
|
+
let stdout = '';
|
|
51
|
+
let stderr = '';
|
|
52
|
+
|
|
53
|
+
child.stdout.on('data', (data) => {
|
|
54
|
+
stdout += data.toString();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.stderr.on('data', (data) => {
|
|
58
|
+
stderr += data.toString();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
child.on('close', (exitCode) => {
|
|
62
|
+
resolve({
|
|
63
|
+
stdout: stdout.trim(),
|
|
64
|
+
stderr: stderr.trim(),
|
|
65
|
+
exitCode: exitCode || 0
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.on('error', (error) => {
|
|
70
|
+
resolve({
|
|
71
|
+
stdout: stdout.trim(),
|
|
72
|
+
stderr: error.message,
|
|
73
|
+
exitCode: 1
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if GitHub CLI is authenticated
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} options - Options
|
|
83
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
84
|
+
* @param {Object} options.logger - Custom logger
|
|
85
|
+
* @returns {Promise<boolean>} True if authenticated
|
|
86
|
+
*/
|
|
87
|
+
export async function isGhAuthenticated(options = {}) {
|
|
88
|
+
const { verbose = false, logger = console } = options;
|
|
89
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
90
|
+
|
|
91
|
+
log.debug(() => 'Checking GitHub CLI authentication status...');
|
|
92
|
+
|
|
93
|
+
const result = await execCommand('gh', ['auth', 'status']);
|
|
94
|
+
|
|
95
|
+
if (result.exitCode !== 0) {
|
|
96
|
+
log.debug(() => `GitHub CLI is not authenticated: ${result.stderr}`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log.debug(() => 'GitHub CLI is authenticated');
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get GitHub username from authenticated user
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} options - Options
|
|
108
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
109
|
+
* @param {Object} options.logger - Custom logger
|
|
110
|
+
* @returns {Promise<string>} GitHub username
|
|
111
|
+
*/
|
|
112
|
+
export async function getGitHubUsername(options = {}) {
|
|
113
|
+
const { verbose = false, logger = console } = options;
|
|
114
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
115
|
+
|
|
116
|
+
log.debug(() => 'Getting GitHub username...');
|
|
117
|
+
|
|
118
|
+
const result = await execCommand('gh', ['api', 'user', '--jq', '.login']);
|
|
119
|
+
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
throw new Error(`Failed to get GitHub username: ${result.stderr}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const username = result.stdout;
|
|
125
|
+
log.debug(() => `GitHub username: ${username}`);
|
|
126
|
+
|
|
127
|
+
return username;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get primary email from GitHub user
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} options - Options
|
|
134
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
135
|
+
* @param {Object} options.logger - Custom logger
|
|
136
|
+
* @returns {Promise<string>} Primary email address
|
|
137
|
+
*/
|
|
138
|
+
export async function getGitHubEmail(options = {}) {
|
|
139
|
+
const { verbose = false, logger = console } = options;
|
|
140
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
141
|
+
|
|
142
|
+
log.debug(() => 'Getting GitHub primary email...');
|
|
143
|
+
|
|
144
|
+
const result = await execCommand('gh', ['api', 'user/emails', '--jq', '.[] | select(.primary==true) | .email']);
|
|
145
|
+
|
|
146
|
+
if (result.exitCode !== 0) {
|
|
147
|
+
throw new Error(`Failed to get GitHub email: ${result.stderr}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const email = result.stdout;
|
|
151
|
+
|
|
152
|
+
if (!email) {
|
|
153
|
+
throw new Error('No primary email found on GitHub account. Please set a primary email in your GitHub settings.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
log.debug(() => `GitHub primary email: ${email}`);
|
|
157
|
+
|
|
158
|
+
return email;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get GitHub user information (username and primary email)
|
|
163
|
+
*
|
|
164
|
+
* @param {Object} options - Options
|
|
165
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
166
|
+
* @param {Object} options.logger - Custom logger
|
|
167
|
+
* @returns {Promise<{username: string, email: string}>} User information
|
|
168
|
+
*/
|
|
169
|
+
export async function getGitHubUserInfo(options = {}) {
|
|
170
|
+
const [username, email] = await Promise.all([
|
|
171
|
+
getGitHubUsername(options),
|
|
172
|
+
getGitHubEmail(options)
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
return { username, email };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Set git config value
|
|
180
|
+
*
|
|
181
|
+
* @param {string} key - Config key (e.g., 'user.name')
|
|
182
|
+
* @param {string} value - Config value
|
|
183
|
+
* @param {Object} options - Options
|
|
184
|
+
* @param {string} options.scope - 'global' or 'local' (default: 'global')
|
|
185
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
186
|
+
* @param {Object} options.logger - Custom logger
|
|
187
|
+
* @returns {Promise<void>}
|
|
188
|
+
*/
|
|
189
|
+
export async function setGitConfig(key, value, options = {}) {
|
|
190
|
+
const { scope = 'global', verbose = false, logger = console } = options;
|
|
191
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
192
|
+
|
|
193
|
+
const scopeFlag = scope === 'local' ? '--local' : '--global';
|
|
194
|
+
|
|
195
|
+
log.debug(() => `Setting git config ${key} = ${value} (${scope})`);
|
|
196
|
+
|
|
197
|
+
const result = await execCommand('git', ['config', scopeFlag, key, value]);
|
|
198
|
+
|
|
199
|
+
if (result.exitCode !== 0) {
|
|
200
|
+
throw new Error(`Failed to set git config ${key}: ${result.stderr}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
log.debug(() => `Successfully set git config ${key}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get git config value
|
|
208
|
+
*
|
|
209
|
+
* @param {string} key - Config key (e.g., 'user.name')
|
|
210
|
+
* @param {Object} options - Options
|
|
211
|
+
* @param {string} options.scope - 'global' or 'local' (default: 'global')
|
|
212
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
213
|
+
* @param {Object} options.logger - Custom logger
|
|
214
|
+
* @returns {Promise<string|null>} Config value or null if not set
|
|
215
|
+
*/
|
|
216
|
+
export async function getGitConfig(key, options = {}) {
|
|
217
|
+
const { scope = 'global', verbose = false, logger = console } = options;
|
|
218
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
219
|
+
|
|
220
|
+
const scopeFlag = scope === 'local' ? '--local' : '--global';
|
|
221
|
+
|
|
222
|
+
log.debug(() => `Getting git config ${key} (${scope})`);
|
|
223
|
+
|
|
224
|
+
const result = await execCommand('git', ['config', scopeFlag, key]);
|
|
225
|
+
|
|
226
|
+
if (result.exitCode !== 0) {
|
|
227
|
+
log.debug(() => `Git config ${key} not set`);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const value = result.stdout;
|
|
232
|
+
log.debug(() => `Git config ${key} = ${value}`);
|
|
233
|
+
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Setup git identity based on GitHub user
|
|
239
|
+
*
|
|
240
|
+
* @param {Object} options - Options
|
|
241
|
+
* @param {string} options.scope - 'global' or 'local' (default: 'global')
|
|
242
|
+
* @param {boolean} options.dryRun - Dry run mode (default: false)
|
|
243
|
+
* @param {boolean} options.verbose - Enable verbose logging (default: false)
|
|
244
|
+
* @param {Object} options.logger - Custom logger (default: console)
|
|
245
|
+
* @returns {Promise<{username: string, email: string}>} Configured identity
|
|
246
|
+
*/
|
|
247
|
+
export async function setupGitIdentity(options = {}) {
|
|
248
|
+
const {
|
|
249
|
+
scope = 'global',
|
|
250
|
+
dryRun = false,
|
|
251
|
+
verbose = false,
|
|
252
|
+
logger = console
|
|
253
|
+
} = options;
|
|
254
|
+
|
|
255
|
+
const log = createDefaultLogger({ verbose, logger });
|
|
256
|
+
|
|
257
|
+
log(() => 'Fetching GitHub user information...');
|
|
258
|
+
|
|
259
|
+
// Get GitHub user info
|
|
260
|
+
const { username, email } = await getGitHubUserInfo({ verbose, logger });
|
|
261
|
+
|
|
262
|
+
log(() => `GitHub user: ${username}`);
|
|
263
|
+
log(() => `GitHub email: ${email}`);
|
|
264
|
+
|
|
265
|
+
if (dryRun) {
|
|
266
|
+
log(() => '');
|
|
267
|
+
log(() => 'DRY MODE: Would configure the following:');
|
|
268
|
+
log(() => ` git config --${scope} user.name "${username}"`);
|
|
269
|
+
log(() => ` git config --${scope} user.email "${email}"`);
|
|
270
|
+
return { username, email };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Set git config
|
|
274
|
+
log(() => '');
|
|
275
|
+
log(() => `Configuring git (${scope})...`);
|
|
276
|
+
|
|
277
|
+
await setGitConfig('user.name', username, { scope, verbose, logger });
|
|
278
|
+
await setGitConfig('user.email', email, { scope, verbose, logger });
|
|
279
|
+
|
|
280
|
+
log(() => 'Git identity configured successfully!');
|
|
281
|
+
|
|
282
|
+
return { username, email };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Verify git identity is configured correctly
|
|
287
|
+
*
|
|
288
|
+
* @param {Object} options - Options
|
|
289
|
+
* @param {string} options.scope - 'global' or 'local' (default: 'global')
|
|
290
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
291
|
+
* @param {Object} options.logger - Custom logger
|
|
292
|
+
* @returns {Promise<{username: string|null, email: string|null}>} Current git identity
|
|
293
|
+
*/
|
|
294
|
+
export async function verifyGitIdentity(options = {}) {
|
|
295
|
+
const { scope = 'global', verbose = false, logger = console } = options;
|
|
296
|
+
|
|
297
|
+
const username = await getGitConfig('user.name', { scope, verbose, logger });
|
|
298
|
+
const email = await getGitConfig('user.email', { scope, verbose, logger });
|
|
299
|
+
|
|
300
|
+
return { username, email };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export default {
|
|
304
|
+
isGhAuthenticated,
|
|
305
|
+
getGitHubUsername,
|
|
306
|
+
getGitHubEmail,
|
|
307
|
+
getGitHubUserInfo,
|
|
308
|
+
setGitConfig,
|
|
309
|
+
getGitConfig,
|
|
310
|
+
setupGitIdentity,
|
|
311
|
+
verifyGitIdentity
|
|
312
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for gh-setup-git-identity core library
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test, assert } from 'test-anywhere';
|
|
6
|
+
import {
|
|
7
|
+
isGhAuthenticated,
|
|
8
|
+
getGitHubUsername,
|
|
9
|
+
getGitHubEmail,
|
|
10
|
+
getGitHubUserInfo,
|
|
11
|
+
getGitConfig,
|
|
12
|
+
verifyGitIdentity
|
|
13
|
+
} from '../src/index.js';
|
|
14
|
+
|
|
15
|
+
// Note: These tests require gh to be authenticated
|
|
16
|
+
// In CI, we may skip integration tests that require auth
|
|
17
|
+
|
|
18
|
+
// Helper to create a silent logger for tests
|
|
19
|
+
const silentLogger = {
|
|
20
|
+
log: () => {},
|
|
21
|
+
error: () => {},
|
|
22
|
+
warn: () => {},
|
|
23
|
+
debug: () => {}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Test: isGhAuthenticated function exists and returns boolean
|
|
27
|
+
test('isGhAuthenticated - returns a boolean', async () => {
|
|
28
|
+
const result = await isGhAuthenticated({ logger: silentLogger });
|
|
29
|
+
assert.equal(typeof result, 'boolean');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Test: getGitConfig function exists
|
|
33
|
+
test('getGitConfig - returns string or null', async () => {
|
|
34
|
+
const result = await getGitConfig('user.name', { logger: silentLogger });
|
|
35
|
+
assert.ok(result === null || typeof result === 'string');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Test: verifyGitIdentity function exists and returns object
|
|
39
|
+
test('verifyGitIdentity - returns object with username and email', async () => {
|
|
40
|
+
const result = await verifyGitIdentity({ logger: silentLogger });
|
|
41
|
+
assert.ok(typeof result === 'object');
|
|
42
|
+
assert.ok('username' in result);
|
|
43
|
+
assert.ok('email' in result);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Integration tests - only run if authenticated
|
|
47
|
+
test('getGitHubUsername - returns username when authenticated', async () => {
|
|
48
|
+
const isAuth = await isGhAuthenticated({ logger: silentLogger });
|
|
49
|
+
if (!isAuth) {
|
|
50
|
+
console.log('Skipping test: gh not authenticated');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const username = await getGitHubUsername({ logger: silentLogger });
|
|
55
|
+
assert.ok(typeof username === 'string');
|
|
56
|
+
assert.ok(username.length > 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('getGitHubEmail - returns email when authenticated', async () => {
|
|
60
|
+
const isAuth = await isGhAuthenticated({ logger: silentLogger });
|
|
61
|
+
if (!isAuth) {
|
|
62
|
+
console.log('Skipping test: gh not authenticated');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const email = await getGitHubEmail({ logger: silentLogger });
|
|
67
|
+
assert.ok(typeof email === 'string');
|
|
68
|
+
assert.ok(email.includes('@'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('getGitHubUserInfo - returns user info when authenticated', async () => {
|
|
72
|
+
const isAuth = await isGhAuthenticated({ logger: silentLogger });
|
|
73
|
+
if (!isAuth) {
|
|
74
|
+
console.log('Skipping test: gh not authenticated');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const info = await getGitHubUserInfo({ logger: silentLogger });
|
|
79
|
+
assert.ok(typeof info === 'object');
|
|
80
|
+
assert.ok(typeof info.username === 'string');
|
|
81
|
+
assert.ok(typeof info.email === 'string');
|
|
82
|
+
assert.ok(info.username.length > 0);
|
|
83
|
+
assert.ok(info.email.includes('@'));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Test: module exports
|
|
87
|
+
test('module exports all expected functions', async () => {
|
|
88
|
+
const module = await import('../src/index.js');
|
|
89
|
+
|
|
90
|
+
assert.ok(typeof module.isGhAuthenticated === 'function');
|
|
91
|
+
assert.ok(typeof module.getGitHubUsername === 'function');
|
|
92
|
+
assert.ok(typeof module.getGitHubEmail === 'function');
|
|
93
|
+
assert.ok(typeof module.getGitHubUserInfo === 'function');
|
|
94
|
+
assert.ok(typeof module.setGitConfig === 'function');
|
|
95
|
+
assert.ok(typeof module.getGitConfig === 'function');
|
|
96
|
+
assert.ok(typeof module.setupGitIdentity === 'function');
|
|
97
|
+
assert.ok(typeof module.verifyGitIdentity === 'function');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Test: default export
|
|
101
|
+
test('default export contains all functions', async () => {
|
|
102
|
+
const module = await import('../src/index.js');
|
|
103
|
+
const defaultExport = module.default;
|
|
104
|
+
|
|
105
|
+
assert.ok(typeof defaultExport === 'object');
|
|
106
|
+
assert.ok(typeof defaultExport.isGhAuthenticated === 'function');
|
|
107
|
+
assert.ok(typeof defaultExport.getGitHubUsername === 'function');
|
|
108
|
+
assert.ok(typeof defaultExport.getGitHubEmail === 'function');
|
|
109
|
+
assert.ok(typeof defaultExport.getGitHubUserInfo === 'function');
|
|
110
|
+
assert.ok(typeof defaultExport.setGitConfig === 'function');
|
|
111
|
+
assert.ok(typeof defaultExport.getGitConfig === 'function');
|
|
112
|
+
assert.ok(typeof defaultExport.setupGitIdentity === 'function');
|
|
113
|
+
assert.ok(typeof defaultExport.verifyGitIdentity === 'function');
|
|
114
|
+
});
|