glab-setup-git-identity 0.6.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.
Files changed (66) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/release.yml +372 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.jscpd.json +20 -0
  6. package/.prettierignore +7 -0
  7. package/.prettierrc +10 -0
  8. package/CHANGELOG.md +143 -0
  9. package/LICENSE +24 -0
  10. package/README.md +455 -0
  11. package/bunfig.toml +3 -0
  12. package/deno.json +7 -0
  13. package/docs/case-studies/issue-13/README.md +195 -0
  14. package/docs/case-studies/issue-13/hive-mind-issue-960.json +23 -0
  15. package/docs/case-studies/issue-13/hive-mind-pr-961-diff.txt +773 -0
  16. package/docs/case-studies/issue-13/hive-mind-pr-961.json +126 -0
  17. package/docs/case-studies/issue-21/README.md +384 -0
  18. package/docs/case-studies/issue-21/ci-logs/run-20803315337.txt +1188 -0
  19. package/docs/case-studies/issue-21/ci-logs/run-20885464993.txt +1310 -0
  20. package/docs/case-studies/issue-21/issue-111-data.txt +15 -0
  21. package/docs/case-studies/issue-21/issue-113-data.txt +15 -0
  22. package/docs/case-studies/issue-21/pr-112-data.json +109 -0
  23. package/docs/case-studies/issue-21/pr-112-diff.patch +1336 -0
  24. package/docs/case-studies/issue-21/pr-114-data.json +126 -0
  25. package/docs/case-studies/issue-21/pr-114-diff.patch +879 -0
  26. package/docs/case-studies/issue-3/README.md +338 -0
  27. package/docs/case-studies/issue-3/created-issues.md +32 -0
  28. package/docs/case-studies/issue-3/issue-data.json +29 -0
  29. package/docs/case-studies/issue-3/original-format-release-notes.mjs +212 -0
  30. package/docs/case-studies/issue-3/reference-pr-59-diff.txt +614 -0
  31. package/docs/case-studies/issue-3/reference-pr-59.json +109 -0
  32. package/docs/case-studies/issue-3/release-v0.1.0.json +9 -0
  33. package/docs/case-studies/issue-3/repositories-with-same-script.json +22 -0
  34. package/docs/case-studies/issue-3/research-notes.md +33 -0
  35. package/docs/case-studies/issue-7/BEST-PRACTICES-COMPARISON.md +334 -0
  36. package/docs/case-studies/issue-7/FORMATTER-COMPARISON.md +649 -0
  37. package/docs/case-studies/issue-7/current-repository-analysis.json +70 -0
  38. package/docs/case-studies/issue-7/effect-template-analysis.json +178 -0
  39. package/eslint.config.js +91 -0
  40. package/examples/basic-usage.js +64 -0
  41. package/experiments/test-changeset-scripts.mjs +303 -0
  42. package/experiments/test-failure-detection.mjs +143 -0
  43. package/experiments/test-format-major-changes.mjs +49 -0
  44. package/experiments/test-format-minor-changes.mjs +52 -0
  45. package/experiments/test-format-no-hash.mjs +43 -0
  46. package/experiments/test-format-patch-changes.mjs +46 -0
  47. package/package.json +80 -0
  48. package/scripts/changeset-version.mjs +75 -0
  49. package/scripts/check-changesets.mjs +67 -0
  50. package/scripts/check-version.mjs +129 -0
  51. package/scripts/create-github-release.mjs +93 -0
  52. package/scripts/create-manual-changeset.mjs +89 -0
  53. package/scripts/detect-code-changes.mjs +194 -0
  54. package/scripts/format-github-release.mjs +83 -0
  55. package/scripts/format-release-notes.mjs +219 -0
  56. package/scripts/instant-version-bump.mjs +172 -0
  57. package/scripts/js-paths.mjs +177 -0
  58. package/scripts/merge-changesets.mjs +263 -0
  59. package/scripts/publish-to-npm.mjs +302 -0
  60. package/scripts/setup-npm.mjs +37 -0
  61. package/scripts/validate-changeset.mjs +265 -0
  62. package/scripts/version-and-commit.mjs +284 -0
  63. package/src/cli.js +386 -0
  64. package/src/index.d.ts +255 -0
  65. package/src/index.js +563 -0
  66. package/tests/index.test.js +137 -0
package/src/index.js ADDED
@@ -0,0 +1,563 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * glab-setup-git-identity - Core library for setting up git identity using GitLab CLI
5
+ *
6
+ * This library provides functionality to:
7
+ * - Check if GitLab CLI is authenticated
8
+ * - Get GitLab user information (username and email)
9
+ * - Configure git user.name and user.email
10
+ */
11
+
12
+ import { $ } from 'command-stream';
13
+
14
+ /**
15
+ * Create a logger instance
16
+ * This can be customized by users when using the library
17
+ */
18
+ function createDefaultLogger(options = {}) {
19
+ const { verbose = false, logger = console } = options;
20
+
21
+ return {
22
+ log: (...args) => logger.log(...args),
23
+ error: (...args) => (logger.error || logger.log)(...args),
24
+ warn: (...args) => (logger.warn || logger.log)(...args),
25
+ debug: (...args) => {
26
+ if (verbose) {
27
+ (logger.debug || logger.log)(...args);
28
+ }
29
+ },
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Default options for glab auth login
35
+ */
36
+ export const defaultAuthOptions = {
37
+ hostname: 'gitlab.com',
38
+ gitProtocol: 'https',
39
+ apiProtocol: 'https',
40
+ useKeyring: false,
41
+ };
42
+
43
+ /**
44
+ * Get the full path to the glab executable
45
+ *
46
+ * This function dynamically detects the glab installation path
47
+ * without depending on any specific installation method.
48
+ *
49
+ * @param {Object} options - Options
50
+ * @param {boolean} options.verbose - Enable verbose logging
51
+ * @param {Object} options.logger - Custom logger
52
+ * @returns {Promise<string>} Full path to glab executable
53
+ * @throws {Error} If glab is not found
54
+ */
55
+ export async function getGlabPath(options = {}) {
56
+ const { verbose = false, logger = console } = options;
57
+ const log = createDefaultLogger({ verbose, logger });
58
+
59
+ log.debug('Detecting glab installation path...');
60
+
61
+ // Use 'which' on Unix-like systems or 'where' on Windows
62
+ const command = process.platform === 'win32' ? 'where' : 'which';
63
+
64
+ try {
65
+ const result = await $`${command} glab`.run({ capture: true });
66
+
67
+ if (result.code !== 0 || !result.stdout) {
68
+ throw new Error(
69
+ 'glab CLI not found. Please install glab: https://gitlab.com/gitlab-org/cli#installation'
70
+ );
71
+ }
72
+
73
+ // Get the first line (in case multiple paths are returned)
74
+ const glabPath = result.stdout.split('\n')[0].trim();
75
+ log.debug(`Found glab at: ${glabPath}`);
76
+
77
+ return glabPath;
78
+ } catch (error) {
79
+ if (error.message.includes('not found')) {
80
+ throw error;
81
+ }
82
+ throw new Error(
83
+ 'glab CLI not found. Please install glab: https://gitlab.com/gitlab-org/cli#installation'
84
+ );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Build glab auth login arguments from options
90
+ * @param {Object} options - Auth options
91
+ * @returns {string[]} Array of CLI arguments
92
+ */
93
+ function buildGlabAuthLoginArgs(options) {
94
+ const {
95
+ hostname,
96
+ gitProtocol,
97
+ apiProtocol,
98
+ apiHost,
99
+ useKeyring,
100
+ jobToken,
101
+ token,
102
+ stdin,
103
+ } = options;
104
+
105
+ const args = ['auth', 'login'];
106
+
107
+ if (hostname) {
108
+ args.push('--hostname', hostname);
109
+ }
110
+ if (gitProtocol) {
111
+ args.push('--git-protocol', gitProtocol);
112
+ }
113
+ if (apiProtocol) {
114
+ args.push('--api-protocol', apiProtocol);
115
+ }
116
+ if (apiHost) {
117
+ args.push('--api-host', apiHost);
118
+ }
119
+ if (useKeyring) {
120
+ args.push('--use-keyring');
121
+ }
122
+
123
+ // Handle authentication method (mutually exclusive)
124
+ if (jobToken) {
125
+ args.push('--job-token', jobToken);
126
+ } else if (token) {
127
+ args.push('--token', token);
128
+ } else if (stdin) {
129
+ args.push('--stdin');
130
+ }
131
+
132
+ return args;
133
+ }
134
+
135
+ /**
136
+ * Run glab auth login interactively
137
+ *
138
+ * @param {Object} options - Options
139
+ * @param {string} options.hostname - GitLab hostname (default: 'gitlab.com')
140
+ * @param {string} options.token - GitLab access token (optional, for non-interactive login)
141
+ * @param {string} options.gitProtocol - Git protocol: 'ssh', 'https', or 'http' (default: 'https')
142
+ * @param {string} options.apiProtocol - API protocol: 'https' or 'http' (default: 'https')
143
+ * @param {string} options.apiHost - Custom API host URL (optional)
144
+ * @param {boolean} options.useKeyring - Store token in OS keyring (default: false)
145
+ * @param {string} options.jobToken - CI job token for authentication (optional)
146
+ * @param {boolean} options.stdin - Read token from stdin (default: false)
147
+ * @param {boolean} options.verbose - Enable verbose logging
148
+ * @param {Object} options.logger - Custom logger
149
+ * @returns {Promise<boolean>} True if login was successful
150
+ */
151
+ export async function runGlabAuthLogin(options = {}) {
152
+ const {
153
+ hostname = defaultAuthOptions.hostname,
154
+ token,
155
+ gitProtocol = defaultAuthOptions.gitProtocol,
156
+ apiProtocol = defaultAuthOptions.apiProtocol,
157
+ apiHost,
158
+ useKeyring = defaultAuthOptions.useKeyring,
159
+ jobToken,
160
+ stdin = false,
161
+ verbose = false,
162
+ logger = console,
163
+ } = options;
164
+
165
+ const log = createDefaultLogger({ verbose, logger });
166
+
167
+ const args = buildGlabAuthLoginArgs({
168
+ hostname,
169
+ gitProtocol,
170
+ apiProtocol,
171
+ apiHost,
172
+ useKeyring,
173
+ jobToken,
174
+ token,
175
+ stdin,
176
+ });
177
+
178
+ log.debug(`Running: glab ${args.join(' ')}`);
179
+
180
+ try {
181
+ const result = await $`glab ${args}`.run({
182
+ mirror: { stdout: true, stderr: true },
183
+ stdin: stdin ? 'inherit' : undefined,
184
+ });
185
+
186
+ if (result.code !== 0) {
187
+ log.error('GitLab CLI authentication failed');
188
+ return false;
189
+ }
190
+
191
+ log.log('\nGitLab CLI authentication successful!');
192
+ return true;
193
+ } catch (error) {
194
+ log.error(`GitLab CLI authentication failed: ${error.message}`);
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Run glab auth setup-git equivalent to configure git to use GitLab CLI as credential helper
201
+ *
202
+ * Unlike GitHub CLI which has `gh auth setup-git`, GitLab CLI doesn't have an equivalent command.
203
+ * This function manually configures git to use `glab auth git-credential` as the credential helper
204
+ * for GitLab HTTPS operations.
205
+ *
206
+ * Without this, git push/pull may fail with "could not read Username" error when using HTTPS protocol.
207
+ *
208
+ * @param {Object} options - Options
209
+ * @param {string} options.hostname - GitLab hostname (default: 'gitlab.com')
210
+ * @param {boolean} options.force - Force setup by overwriting existing credential helper config (default: false)
211
+ * @param {boolean} options.verbose - Enable verbose logging
212
+ * @param {Object} options.logger - Custom logger
213
+ * @returns {Promise<boolean>} True if setup was successful
214
+ */
215
+ export async function runGlabAuthSetupGit(options = {}) {
216
+ const {
217
+ hostname = defaultAuthOptions.hostname,
218
+ force = false,
219
+ verbose = false,
220
+ logger = console,
221
+ } = options;
222
+
223
+ const log = createDefaultLogger({ verbose, logger });
224
+
225
+ log.debug('Configuring git credential helper for GitLab CLI...');
226
+
227
+ try {
228
+ // Get the full path to glab executable
229
+ const glabPath = await getGlabPath({ verbose, logger });
230
+
231
+ // Build the credential helper URL based on hostname
232
+ const credentialUrl = `https://${hostname}`;
233
+
234
+ // The credential helper command - uses the dynamically detected glab path
235
+ const credentialHelper = `!${glabPath} auth git-credential`;
236
+
237
+ // Check if there's an existing credential helper for this host
238
+ try {
239
+ const existingResult =
240
+ await $`git config --global --get credential.${credentialUrl}.helper`.run(
241
+ { capture: true }
242
+ );
243
+
244
+ if (existingResult.code === 0 && existingResult.stdout && !force) {
245
+ log.debug(
246
+ `Existing credential helper found for ${hostname}: ${existingResult.stdout.trim()}`
247
+ );
248
+ log.log(
249
+ `Git credential helper already configured for ${hostname}. Use force: true to overwrite.`
250
+ );
251
+ return true;
252
+ }
253
+ } catch {
254
+ // No existing helper, proceed with setup
255
+ }
256
+
257
+ // First, clear any existing credential helpers for this host
258
+ // This ensures we have a clean state
259
+ log.debug(`Clearing existing credential helpers for ${credentialUrl}...`);
260
+
261
+ // Set an empty helper first to clear the chain (ignore errors if not set)
262
+ try {
263
+ await $`git config --global credential.${credentialUrl}.helper ""`.run({
264
+ capture: true,
265
+ });
266
+ } catch {
267
+ // Ignore errors if not set
268
+ }
269
+
270
+ // Add the glab credential helper
271
+ log.debug(`Setting credential helper: ${credentialHelper}`);
272
+
273
+ const result =
274
+ await $`git config --global --add credential.${credentialUrl}.helper ${credentialHelper}`.run(
275
+ { capture: true }
276
+ );
277
+
278
+ if (result.code !== 0) {
279
+ log.error(`Failed to set git credential helper: ${result.stderr}`);
280
+ return false;
281
+ }
282
+
283
+ log.log(`Git credential helper configured for ${hostname}`);
284
+ log.debug(` URL: ${credentialUrl}`);
285
+ log.debug(` Helper: ${credentialHelper}`);
286
+
287
+ return true;
288
+ } catch (error) {
289
+ log.error(`Failed to setup git credential helper: ${error.message}`);
290
+ return false;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Check if GitLab CLI is authenticated
296
+ *
297
+ * @param {Object} options - Options
298
+ * @param {string} options.hostname - GitLab hostname to check (optional)
299
+ * @param {boolean} options.verbose - Enable verbose logging
300
+ * @param {Object} options.logger - Custom logger
301
+ * @returns {Promise<boolean>} True if authenticated
302
+ */
303
+ export async function isGlabAuthenticated(options = {}) {
304
+ const { hostname, verbose = false, logger = console } = options;
305
+ const log = createDefaultLogger({ verbose, logger });
306
+
307
+ log.debug('Checking GitLab CLI authentication status...');
308
+
309
+ const args = ['auth', 'status'];
310
+ if (hostname) {
311
+ args.push('--hostname', hostname);
312
+ }
313
+
314
+ try {
315
+ const result = await $`glab ${args}`.run({ capture: true });
316
+
317
+ if (result.code !== 0) {
318
+ log.debug(`GitLab CLI is not authenticated: ${result.stderr}`);
319
+ return false;
320
+ }
321
+
322
+ log.debug('GitLab CLI is authenticated');
323
+ return true;
324
+ } catch (error) {
325
+ log.debug(`GitLab CLI is not authenticated: ${error.message}`);
326
+ return false;
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Get GitLab username from authenticated user
332
+ *
333
+ * @param {Object} options - Options
334
+ * @param {string} options.hostname - GitLab hostname (optional)
335
+ * @param {boolean} options.verbose - Enable verbose logging
336
+ * @param {Object} options.logger - Custom logger
337
+ * @returns {Promise<string>} GitLab username
338
+ */
339
+ export async function getGitLabUsername(options = {}) {
340
+ const { hostname, verbose = false, logger = console } = options;
341
+ const log = createDefaultLogger({ verbose, logger });
342
+
343
+ log.debug('Getting GitLab username...');
344
+
345
+ const args = ['api', 'user', '--jq', '.username'];
346
+ if (hostname) {
347
+ args.push('--hostname', hostname);
348
+ }
349
+
350
+ const result = await $`glab ${args}`.run({ capture: true });
351
+
352
+ if (result.code !== 0) {
353
+ throw new Error(`Failed to get GitLab username: ${result.stderr}`);
354
+ }
355
+
356
+ const username = result.stdout.trim();
357
+ log.debug(`GitLab username: ${username}`);
358
+
359
+ return username;
360
+ }
361
+
362
+ /**
363
+ * Get primary email from GitLab user
364
+ *
365
+ * @param {Object} options - Options
366
+ * @param {string} options.hostname - GitLab hostname (optional)
367
+ * @param {boolean} options.verbose - Enable verbose logging
368
+ * @param {Object} options.logger - Custom logger
369
+ * @returns {Promise<string>} Primary email address
370
+ */
371
+ export async function getGitLabEmail(options = {}) {
372
+ const { hostname, verbose = false, logger = console } = options;
373
+ const log = createDefaultLogger({ verbose, logger });
374
+
375
+ log.debug('Getting GitLab primary email...');
376
+
377
+ const args = ['api', 'user', '--jq', '.email'];
378
+ if (hostname) {
379
+ args.push('--hostname', hostname);
380
+ }
381
+
382
+ const result = await $`glab ${args}`.run({ capture: true });
383
+
384
+ if (result.code !== 0) {
385
+ throw new Error(`Failed to get GitLab email: ${result.stderr}`);
386
+ }
387
+
388
+ const email = result.stdout.trim();
389
+
390
+ if (!email) {
391
+ throw new Error(
392
+ 'No email found on GitLab account. Please set a primary email in your GitLab settings.'
393
+ );
394
+ }
395
+
396
+ log.debug(`GitLab primary email: ${email}`);
397
+
398
+ return email;
399
+ }
400
+
401
+ /**
402
+ * Get GitLab user information (username and primary email)
403
+ *
404
+ * @param {Object} options - Options
405
+ * @param {string} options.hostname - GitLab hostname (optional)
406
+ * @param {boolean} options.verbose - Enable verbose logging
407
+ * @param {Object} options.logger - Custom logger
408
+ * @returns {Promise<{username: string, email: string}>} User information
409
+ */
410
+ export async function getGitLabUserInfo(options = {}) {
411
+ const [username, email] = await Promise.all([
412
+ getGitLabUsername(options),
413
+ getGitLabEmail(options),
414
+ ]);
415
+
416
+ return { username, email };
417
+ }
418
+
419
+ /**
420
+ * Set git config value
421
+ *
422
+ * @param {string} key - Config key (e.g., 'user.name')
423
+ * @param {string} value - Config value
424
+ * @param {Object} options - Options
425
+ * @param {string} options.scope - 'global' or 'local' (default: 'global')
426
+ * @param {boolean} options.verbose - Enable verbose logging
427
+ * @param {Object} options.logger - Custom logger
428
+ * @returns {Promise<void>}
429
+ */
430
+ export async function setGitConfig(key, value, options = {}) {
431
+ const { scope = 'global', verbose = false, logger = console } = options;
432
+ const log = createDefaultLogger({ verbose, logger });
433
+
434
+ const scopeFlag = scope === 'local' ? '--local' : '--global';
435
+
436
+ log.debug(`Setting git config ${key} = ${value} (${scope})`);
437
+
438
+ const result = await $`git config ${scopeFlag} ${key} ${value}`.run({
439
+ capture: true,
440
+ });
441
+
442
+ if (result.code !== 0) {
443
+ throw new Error(`Failed to set git config ${key}: ${result.stderr}`);
444
+ }
445
+
446
+ log.debug(`Successfully set git config ${key}`);
447
+ }
448
+
449
+ /**
450
+ * Get git config value
451
+ *
452
+ * @param {string} key - Config key (e.g., 'user.name')
453
+ * @param {Object} options - Options
454
+ * @param {string} options.scope - 'global' or 'local' (default: 'global')
455
+ * @param {boolean} options.verbose - Enable verbose logging
456
+ * @param {Object} options.logger - Custom logger
457
+ * @returns {Promise<string|null>} Config value or null if not set
458
+ */
459
+ export async function getGitConfig(key, options = {}) {
460
+ const { scope = 'global', verbose = false, logger = console } = options;
461
+ const log = createDefaultLogger({ verbose, logger });
462
+
463
+ const scopeFlag = scope === 'local' ? '--local' : '--global';
464
+
465
+ log.debug(`Getting git config ${key} (${scope})`);
466
+
467
+ const result = await $`git config ${scopeFlag} ${key}`.run({ capture: true });
468
+
469
+ if (result.code !== 0) {
470
+ log.debug(`Git config ${key} not set`);
471
+ return null;
472
+ }
473
+
474
+ const value = result.stdout.trim();
475
+ log.debug(`Git config ${key} = ${value}`);
476
+
477
+ return value;
478
+ }
479
+
480
+ /**
481
+ * Setup git identity based on GitLab user
482
+ *
483
+ * @param {Object} options - Options
484
+ * @param {string} options.hostname - GitLab hostname (optional)
485
+ * @param {string} options.scope - 'global' or 'local' (default: 'global')
486
+ * @param {boolean} options.dryRun - Dry run mode (default: false)
487
+ * @param {boolean} options.verbose - Enable verbose logging (default: false)
488
+ * @param {Object} options.logger - Custom logger (default: console)
489
+ * @returns {Promise<{username: string, email: string}>} Configured identity
490
+ */
491
+ export async function setupGitIdentity(options = {}) {
492
+ const {
493
+ hostname,
494
+ scope = 'global',
495
+ dryRun = false,
496
+ verbose = false,
497
+ logger = console,
498
+ } = options;
499
+
500
+ const log = createDefaultLogger({ verbose, logger });
501
+
502
+ log.log('\nFetching GitLab user information...');
503
+
504
+ // Get GitLab user info
505
+ const { username, email } = await getGitLabUserInfo({
506
+ hostname,
507
+ verbose,
508
+ logger,
509
+ });
510
+
511
+ log.log(` GitLab user: ${username}`);
512
+ log.log(` GitLab email: ${email}`);
513
+
514
+ if (dryRun) {
515
+ log.log('DRY MODE: Would configure the following:');
516
+ log.log(` git config --${scope} user.name "${username}"`);
517
+ log.log(` git config --${scope} user.email "${email}"`);
518
+ return { username, email };
519
+ }
520
+
521
+ // Set git config
522
+ log.log(`\nConfiguring git (${scope})...`);
523
+
524
+ await setGitConfig('user.name', username, { scope, verbose, logger });
525
+ await setGitConfig('user.email', email, { scope, verbose, logger });
526
+
527
+ log.log(' Git identity configured successfully!');
528
+
529
+ return { username, email };
530
+ }
531
+
532
+ /**
533
+ * Verify git identity is configured correctly
534
+ *
535
+ * @param {Object} options - Options
536
+ * @param {string} options.scope - 'global' or 'local' (default: 'global')
537
+ * @param {boolean} options.verbose - Enable verbose logging
538
+ * @param {Object} options.logger - Custom logger
539
+ * @returns {Promise<{username: string|null, email: string|null}>} Current git identity
540
+ */
541
+ export async function verifyGitIdentity(options = {}) {
542
+ const { scope = 'global', verbose = false, logger = console } = options;
543
+
544
+ const username = await getGitConfig('user.name', { scope, verbose, logger });
545
+ const email = await getGitConfig('user.email', { scope, verbose, logger });
546
+
547
+ return { username, email };
548
+ }
549
+
550
+ export default {
551
+ defaultAuthOptions,
552
+ getGlabPath,
553
+ isGlabAuthenticated,
554
+ runGlabAuthLogin,
555
+ runGlabAuthSetupGit,
556
+ getGitLabUsername,
557
+ getGitLabEmail,
558
+ getGitLabUserInfo,
559
+ setGitConfig,
560
+ getGitConfig,
561
+ setupGitIdentity,
562
+ verifyGitIdentity,
563
+ };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Tests for glab-setup-git-identity
3
+ * Works with Node.js, Bun, and Deno using test-anywhere
4
+ */
5
+
6
+ import { describe, it, expect } from 'test-anywhere';
7
+ import {
8
+ defaultAuthOptions,
9
+ getGitConfig,
10
+ setGitConfig,
11
+ verifyGitIdentity,
12
+ getGlabPath,
13
+ runGlabAuthSetupGit,
14
+ } from '../src/index.js';
15
+
16
+ describe('defaultAuthOptions', () => {
17
+ it('should have correct default hostname', () => {
18
+ expect(defaultAuthOptions.hostname).toBe('gitlab.com');
19
+ });
20
+
21
+ it('should have correct default git protocol', () => {
22
+ expect(defaultAuthOptions.gitProtocol).toBe('https');
23
+ });
24
+
25
+ it('should have correct default api protocol', () => {
26
+ expect(defaultAuthOptions.apiProtocol).toBe('https');
27
+ });
28
+
29
+ it('should have useKeyring disabled by default', () => {
30
+ expect(defaultAuthOptions.useKeyring).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe('getGitConfig', () => {
35
+ it('should return null for non-existent config key', async () => {
36
+ // Use a unique key that definitely doesn't exist
37
+ const value = await getGitConfig('glab-test.nonexistent-key-12345', {
38
+ scope: 'global',
39
+ });
40
+ expect(value).toBe(null);
41
+ });
42
+
43
+ it('should return existing git config value', async () => {
44
+ // user.name is typically set in most git environments
45
+ const value = await getGitConfig('user.name', { scope: 'global' });
46
+ // Just verify it returns a string or null (depends on environment)
47
+ expect(typeof value === 'string' || value === null).toBe(true);
48
+ });
49
+ });
50
+
51
+ describe('setGitConfig', () => {
52
+ it('should set and then get a git config value in local scope', async () => {
53
+ // Use local scope to avoid modifying global git config
54
+ const testKey = 'glab-test.test-value';
55
+ const testValue = `test-${Date.now()}`;
56
+
57
+ // Set the value
58
+ await setGitConfig(testKey, testValue, { scope: 'local' });
59
+
60
+ // Get the value back
61
+ const retrievedValue = await getGitConfig(testKey, { scope: 'local' });
62
+ expect(retrievedValue).toBe(testValue);
63
+
64
+ // Clean up - unset the test value
65
+ const { spawn } = await import('node:child_process');
66
+ await new Promise((resolve) => {
67
+ const child = spawn('git', ['config', '--local', '--unset', testKey]);
68
+ child.on('close', resolve);
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('verifyGitIdentity', () => {
74
+ it('should return an object with username and email properties', async () => {
75
+ const identity = await verifyGitIdentity({ scope: 'global' });
76
+
77
+ expect(typeof identity).toBe('object');
78
+ expect('username' in identity).toBe(true);
79
+ expect('email' in identity).toBe(true);
80
+ });
81
+
82
+ it('should return null or string for each property', async () => {
83
+ const identity = await verifyGitIdentity({ scope: 'global' });
84
+
85
+ expect(
86
+ identity.username === null || typeof identity.username === 'string'
87
+ ).toBe(true);
88
+ expect(identity.email === null || typeof identity.email === 'string').toBe(
89
+ true
90
+ );
91
+ });
92
+ });
93
+
94
+ describe('getGlabPath', () => {
95
+ it('should be a function', () => {
96
+ expect(typeof getGlabPath).toBe('function');
97
+ });
98
+
99
+ it('should return a promise that resolves or rejects', async () => {
100
+ // We can't test the actual path without glab installed,
101
+ // but we can verify it returns a promise and handles gracefully
102
+ try {
103
+ const result = await getGlabPath();
104
+ // If glab is installed, it should return a string path
105
+ expect(typeof result).toBe('string');
106
+ } catch {
107
+ // If glab is not installed, it should throw an error
108
+ // This is expected behavior
109
+ expect(true).toBe(true);
110
+ }
111
+ });
112
+ });
113
+
114
+ describe('runGlabAuthSetupGit', () => {
115
+ it('should be a function', () => {
116
+ expect(typeof runGlabAuthSetupGit).toBe('function');
117
+ });
118
+
119
+ it('should return a promise that resolves or rejects', async () => {
120
+ // We can't test the actual setup without glab installed,
121
+ // but we can verify it returns a promise and handles gracefully
122
+ try {
123
+ const result = await runGlabAuthSetupGit();
124
+ // If glab is installed and setup succeeds, it should return true
125
+ expect(typeof result).toBe('boolean');
126
+ } catch {
127
+ // If glab is not installed, it should throw an error
128
+ // This is expected behavior
129
+ expect(true).toBe(true);
130
+ }
131
+ });
132
+ });
133
+
134
+ // Note: Tests for isGlabAuthenticated, getGitLabUsername, getGitLabEmail,
135
+ // getGitLabUserInfo, runGlabAuthLogin, and setupGitIdentity require
136
+ // an authenticated glab CLI environment and are better suited for
137
+ // integration tests or manual testing.