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/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
+ });