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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +372 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +20 -0
- package/.prettierignore +7 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +143 -0
- package/LICENSE +24 -0
- package/README.md +455 -0
- package/bunfig.toml +3 -0
- package/deno.json +7 -0
- package/docs/case-studies/issue-13/README.md +195 -0
- package/docs/case-studies/issue-13/hive-mind-issue-960.json +23 -0
- package/docs/case-studies/issue-13/hive-mind-pr-961-diff.txt +773 -0
- package/docs/case-studies/issue-13/hive-mind-pr-961.json +126 -0
- package/docs/case-studies/issue-21/README.md +384 -0
- package/docs/case-studies/issue-21/ci-logs/run-20803315337.txt +1188 -0
- package/docs/case-studies/issue-21/ci-logs/run-20885464993.txt +1310 -0
- package/docs/case-studies/issue-21/issue-111-data.txt +15 -0
- package/docs/case-studies/issue-21/issue-113-data.txt +15 -0
- package/docs/case-studies/issue-21/pr-112-data.json +109 -0
- package/docs/case-studies/issue-21/pr-112-diff.patch +1336 -0
- package/docs/case-studies/issue-21/pr-114-data.json +126 -0
- package/docs/case-studies/issue-21/pr-114-diff.patch +879 -0
- package/docs/case-studies/issue-3/README.md +338 -0
- package/docs/case-studies/issue-3/created-issues.md +32 -0
- package/docs/case-studies/issue-3/issue-data.json +29 -0
- package/docs/case-studies/issue-3/original-format-release-notes.mjs +212 -0
- package/docs/case-studies/issue-3/reference-pr-59-diff.txt +614 -0
- package/docs/case-studies/issue-3/reference-pr-59.json +109 -0
- package/docs/case-studies/issue-3/release-v0.1.0.json +9 -0
- package/docs/case-studies/issue-3/repositories-with-same-script.json +22 -0
- package/docs/case-studies/issue-3/research-notes.md +33 -0
- package/docs/case-studies/issue-7/BEST-PRACTICES-COMPARISON.md +334 -0
- package/docs/case-studies/issue-7/FORMATTER-COMPARISON.md +649 -0
- package/docs/case-studies/issue-7/current-repository-analysis.json +70 -0
- package/docs/case-studies/issue-7/effect-template-analysis.json +178 -0
- package/eslint.config.js +91 -0
- package/examples/basic-usage.js +64 -0
- package/experiments/test-changeset-scripts.mjs +303 -0
- package/experiments/test-failure-detection.mjs +143 -0
- package/experiments/test-format-major-changes.mjs +49 -0
- package/experiments/test-format-minor-changes.mjs +52 -0
- package/experiments/test-format-no-hash.mjs +43 -0
- package/experiments/test-format-patch-changes.mjs +46 -0
- package/package.json +80 -0
- package/scripts/changeset-version.mjs +75 -0
- package/scripts/check-changesets.mjs +67 -0
- package/scripts/check-version.mjs +129 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +89 -0
- package/scripts/detect-code-changes.mjs +194 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +219 -0
- package/scripts/instant-version-bump.mjs +172 -0
- package/scripts/js-paths.mjs +177 -0
- package/scripts/merge-changesets.mjs +263 -0
- package/scripts/publish-to-npm.mjs +302 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +265 -0
- package/scripts/version-and-commit.mjs +284 -0
- package/src/cli.js +386 -0
- package/src/index.d.ts +255 -0
- package/src/index.js +563 -0
- 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.
|