buddy-workbench 0.1.70 → 0.1.72
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/package.json
CHANGED
|
@@ -54,6 +54,131 @@ async function getJiraIssueSummary(issueKey) {
|
|
|
54
54
|
return summary;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function transformFieldValue(val) {
|
|
58
|
+
if (val === null || val === undefined) return undefined;
|
|
59
|
+
|
|
60
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
61
|
+
return val;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(val)) {
|
|
65
|
+
if (val.length === 0) return undefined;
|
|
66
|
+
const items = val.map((item) => transformFieldValue(item)).filter((item) => item !== undefined);
|
|
67
|
+
return items.length > 0 ? items : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof val === 'object') {
|
|
71
|
+
const keys = Object.keys(val);
|
|
72
|
+
if (keys.length === 0) return undefined;
|
|
73
|
+
|
|
74
|
+
// Support cascading select (parent value + child value)
|
|
75
|
+
if (val.value !== undefined && val.child && typeof val.child === 'object') {
|
|
76
|
+
const childVal = transformFieldValue(val.child);
|
|
77
|
+
if (childVal) {
|
|
78
|
+
return { value: String(val.value), child: childVal };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (val.id !== undefined && val.id !== null) {
|
|
83
|
+
return { id: String(val.id) };
|
|
84
|
+
}
|
|
85
|
+
if (val.accountId !== undefined && val.accountId !== null) {
|
|
86
|
+
return { accountId: String(val.accountId) };
|
|
87
|
+
}
|
|
88
|
+
if (val.value !== undefined && val.value !== null) {
|
|
89
|
+
return { value: String(val.value) };
|
|
90
|
+
}
|
|
91
|
+
if (val.key !== undefined && val.key !== null) {
|
|
92
|
+
return { key: String(val.key) };
|
|
93
|
+
}
|
|
94
|
+
if (val.name !== undefined && val.name !== null) {
|
|
95
|
+
return { name: String(val.name) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractCloneableFields(origFields) {
|
|
103
|
+
if (!origFields || typeof origFields !== 'object') return {};
|
|
104
|
+
|
|
105
|
+
const fields = {};
|
|
106
|
+
|
|
107
|
+
// Standard fields
|
|
108
|
+
if (origFields.description !== undefined && origFields.description !== null) {
|
|
109
|
+
fields.description = origFields.description;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (origFields.priority) {
|
|
113
|
+
if (origFields.priority.id) {
|
|
114
|
+
fields.priority = { id: String(origFields.priority.id) };
|
|
115
|
+
} else if (origFields.priority.name) {
|
|
116
|
+
fields.priority = { name: String(origFields.priority.name) };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(origFields.components) && origFields.components.length > 0) {
|
|
121
|
+
const components = origFields.components
|
|
122
|
+
.map((c) => (c.id ? { id: String(c.id) } : c.name ? { name: String(c.name) } : null))
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
if (components.length > 0) fields.components = components;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (Array.isArray(origFields.labels) && origFields.labels.length > 0) {
|
|
128
|
+
const labels = origFields.labels.map((l) => String(l)).filter(Boolean);
|
|
129
|
+
if (labels.length > 0) fields.labels = labels;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (Array.isArray(origFields.fixVersions) && origFields.fixVersions.length > 0) {
|
|
133
|
+
const fixVersions = origFields.fixVersions
|
|
134
|
+
.map((v) => (v.id ? { id: String(v.id) } : v.name ? { name: String(v.name) } : null))
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
if (fixVersions.length > 0) fields.fixVersions = fixVersions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (Array.isArray(origFields.versions) && origFields.versions.length > 0) {
|
|
140
|
+
const versions = origFields.versions
|
|
141
|
+
.map((v) => (v.id ? { id: String(v.id) } : v.name ? { name: String(v.name) } : null))
|
|
142
|
+
.filter(Boolean);
|
|
143
|
+
if (versions.length > 0) fields.versions = versions;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof origFields.environment === 'string' && origFields.environment) {
|
|
147
|
+
fields.environment = origFields.environment;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (typeof origFields.duedate === 'string' && origFields.duedate) {
|
|
151
|
+
fields.duedate = origFields.duedate;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (origFields.security) {
|
|
155
|
+
if (origFields.security.id) fields.security = { id: String(origFields.security.id) };
|
|
156
|
+
else if (origFields.security.name) fields.security = { name: String(origFields.security.name) };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (origFields.parent) {
|
|
160
|
+
if (origFields.parent.id) fields.parent = { id: String(origFields.parent.id) };
|
|
161
|
+
else if (origFields.parent.key) fields.parent = { key: String(origFields.parent.key) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (origFields.assignee) {
|
|
165
|
+
if (origFields.assignee.accountId) fields.assignee = { accountId: String(origFields.assignee.accountId) };
|
|
166
|
+
else if (origFields.assignee.name) fields.assignee = { name: String(origFields.assignee.name) };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Custom fields starting with customfield_
|
|
170
|
+
for (const [key, val] of Object.entries(origFields)) {
|
|
171
|
+
if (key.startsWith('customfield_') && val !== null && val !== undefined) {
|
|
172
|
+
const transformed = transformFieldValue(val);
|
|
173
|
+
if (transformed !== undefined) {
|
|
174
|
+
fields[key] = transformed;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return fields;
|
|
180
|
+
}
|
|
181
|
+
|
|
57
182
|
// List all filters
|
|
58
183
|
router.get('/', (_req, res) => {
|
|
59
184
|
res.json(listJiraFilters());
|
|
@@ -271,12 +396,11 @@ router.post('/issues/clone', async (req, res) => {
|
|
|
271
396
|
recordApiError({ source: 'Jira API (Clone Issue)', method: 'GET', url: myselfUrl, message: error.message || 'Failed to fetch current user.' });
|
|
272
397
|
}
|
|
273
398
|
|
|
274
|
-
// Fetch original issue details to preserve issue type, project, description, priority
|
|
399
|
+
// Fetch original issue details to preserve issue type, project, description, priority, components, labels, fixVersions, versions, environment, duedate, security, parent, custom fields
|
|
400
|
+
let origFields = null;
|
|
275
401
|
let origIssueType = null;
|
|
276
402
|
let origProjectKey = null;
|
|
277
403
|
let origSummary = null;
|
|
278
|
-
let origDescription = null;
|
|
279
|
-
let origPriority = null;
|
|
280
404
|
|
|
281
405
|
if (issueKey) {
|
|
282
406
|
const issueUrl = `https://${jiraHost}/rest/api/2/issue/${encodeURIComponent(issueKey)}`;
|
|
@@ -286,29 +410,19 @@ router.post('/issues/clone', async (req, res) => {
|
|
|
286
410
|
recordApiError({ source: 'Jira API (Clone Issue)', method: 'GET', url: issueUrl, status: origRes.status, message: `Jira issue API returned ${origRes.status}: ${origRes.statusText || 'Failed to fetch original issue.'}` });
|
|
287
411
|
}
|
|
288
412
|
if (origRes.status === 200 && origRes.data?.fields) {
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
if (
|
|
292
|
-
origIssueType = { id:
|
|
293
|
-
} else if (
|
|
294
|
-
origIssueType = { name:
|
|
413
|
+
origFields = origRes.data.fields;
|
|
414
|
+
if (origFields.issuetype) {
|
|
415
|
+
if (origFields.issuetype.id) {
|
|
416
|
+
origIssueType = { id: String(origFields.issuetype.id) };
|
|
417
|
+
} else if (origFields.issuetype.name) {
|
|
418
|
+
origIssueType = { name: String(origFields.issuetype.name) };
|
|
295
419
|
}
|
|
296
420
|
}
|
|
297
|
-
if (
|
|
298
|
-
origProjectKey =
|
|
299
|
-
}
|
|
300
|
-
if (fields.summary) {
|
|
301
|
-
origSummary = String(fields.summary).trim();
|
|
421
|
+
if (origFields.project?.key) {
|
|
422
|
+
origProjectKey = origFields.project.key;
|
|
302
423
|
}
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
if (fields.priority) {
|
|
307
|
-
if (fields.priority.id) {
|
|
308
|
-
origPriority = { id: fields.priority.id };
|
|
309
|
-
} else if (fields.priority.name) {
|
|
310
|
-
origPriority = { name: fields.priority.name };
|
|
311
|
-
}
|
|
424
|
+
if (origFields.summary) {
|
|
425
|
+
origSummary = String(origFields.summary).trim();
|
|
312
426
|
}
|
|
313
427
|
}
|
|
314
428
|
} catch (error) {
|
|
@@ -321,26 +435,60 @@ router.post('/issues/clone', async (req, res) => {
|
|
|
321
435
|
const projectKey = origProjectKey || (issueKey ? issueKey.split('-')[0] : '');
|
|
322
436
|
const requestedSummary = summary.trim();
|
|
323
437
|
const cloneSummary = origSummary || requestedSummary;
|
|
438
|
+
|
|
439
|
+
// Extract all cloneable standard & custom fields from original issue
|
|
440
|
+
const cloneableFields = extractCloneableFields(origFields);
|
|
441
|
+
|
|
442
|
+
// Override assignee with current authenticated user if resolved
|
|
443
|
+
if (assigneeField) {
|
|
444
|
+
cloneableFields.assignee = assigneeField;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Core fields required for issue creation
|
|
448
|
+
const coreFields = {
|
|
449
|
+
summary: cloneSummary,
|
|
450
|
+
...(projectKey ? { project: { key: projectKey } } : {}),
|
|
451
|
+
issuetype: targetIssueType
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
let activeFields = { ...cloneableFields, ...coreFields };
|
|
455
|
+
let pendingPutFields = {};
|
|
456
|
+
|
|
324
457
|
const url = `https://${jiraHost}/rest/api/2/issue`;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
458
|
+
let response = await httpClient.post(url, { fields: activeFields }, { headers });
|
|
459
|
+
|
|
460
|
+
// Handle field rejection on POST creation (e.g. fields not configured on Create Screen)
|
|
461
|
+
let maxRetries = 3;
|
|
462
|
+
while (response.status !== 201 && response.status !== 200 && maxRetries > 0) {
|
|
463
|
+
maxRetries--;
|
|
464
|
+
const errFields = response.data?.errors ? Object.keys(response.data.errors) : [];
|
|
465
|
+
if (errFields.length > 0) {
|
|
466
|
+
let strippedAny = false;
|
|
467
|
+
for (const errField of errFields) {
|
|
468
|
+
if (activeFields[errField] !== undefined && !['summary', 'project', 'issuetype'].includes(errField)) {
|
|
469
|
+
pendingPutFields[errField] = activeFields[errField];
|
|
470
|
+
delete activeFields[errField];
|
|
471
|
+
strippedAny = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (strippedAny) {
|
|
475
|
+
response = await httpClient.post(url, { fields: activeFields }, { headers });
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
333
478
|
}
|
|
334
|
-
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
335
481
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
482
|
+
// Fallback attempt with minimal core fields if POST creation still failed
|
|
483
|
+
if (response.status !== 201 && response.status !== 200) {
|
|
484
|
+
for (const [k, v] of Object.entries(activeFields)) {
|
|
485
|
+
if (!['summary', 'project', 'issuetype'].includes(k)) {
|
|
486
|
+
pendingPutFields[k] = v;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
339
489
|
const fallbackPayload = {
|
|
340
490
|
fields: {
|
|
341
|
-
|
|
342
|
-
...(projectKey ? { project: { key: projectKey } } : {}),
|
|
343
|
-
issuetype: targetIssueType,
|
|
491
|
+
...coreFields,
|
|
344
492
|
...(assigneeField ? { assignee: assigneeField } : {})
|
|
345
493
|
}
|
|
346
494
|
};
|
|
@@ -358,7 +506,28 @@ router.post('/issues/clone', async (req, res) => {
|
|
|
358
506
|
const created = response.data || {};
|
|
359
507
|
const createdKey = created.key || issueKey;
|
|
360
508
|
|
|
361
|
-
//
|
|
509
|
+
// Apply pending fields via PUT update if any fields could not be set on creation screen
|
|
510
|
+
if (createdKey && Object.keys(pendingPutFields).length > 0) {
|
|
511
|
+
const updateUrl = `https://${jiraHost}/rest/api/2/issue/${encodeURIComponent(createdKey)}`;
|
|
512
|
+
try {
|
|
513
|
+
let putRes = await httpClient.put(updateUrl, { fields: pendingPutFields }, { headers });
|
|
514
|
+
if (putRes.status < 200 || putRes.status >= 300) {
|
|
515
|
+
const putErrFields = putRes.data?.errors ? Object.keys(putRes.data.errors) : [];
|
|
516
|
+
if (putErrFields.length > 0) {
|
|
517
|
+
for (const ef of putErrFields) {
|
|
518
|
+
delete pendingPutFields[ef];
|
|
519
|
+
}
|
|
520
|
+
if (Object.keys(pendingPutFields).length > 0) {
|
|
521
|
+
await httpClient.put(updateUrl, { fields: pendingPutFields }, { headers });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
recordApiError({ source: 'Jira API (Clone Issue PUT fields)', method: 'PUT', url: `https://${jiraHost}/rest/api/2/issue/${createdKey}`, message: err.message || 'Failed to PUT cloned fields.' });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Fallback: Assign explicitly if not assigned during creation/PUT
|
|
362
531
|
if (createdKey && assigneeField) {
|
|
363
532
|
const assignUrl = `https://${jiraHost}/rest/api/2/issue/${createdKey}/assignee`;
|
|
364
533
|
try {
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, dirname, join } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
const NPM_KEYS = ['prefix', 'registry', 'strict-ssl', 'cache', 'proxy', 'https-proxy'];
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
function getNvmDir() {
|
|
11
|
+
const home = homedir();
|
|
12
|
+
const candidates = [
|
|
13
|
+
process.env.NVM_DIR,
|
|
14
|
+
join(home, '.nvm'),
|
|
15
|
+
'/opt/homebrew/opt/nvm',
|
|
16
|
+
'/usr/local/opt/nvm'
|
|
17
|
+
].filter(Boolean);
|
|
18
|
+
for (const dir of candidates) {
|
|
19
|
+
if (existsSync(join(dir, 'nvm.sh'))) return dir;
|
|
20
|
+
}
|
|
21
|
+
return process.env.NVM_DIR || join(home, '.nvm');
|
|
22
|
+
}
|
|
10
23
|
|
|
11
24
|
function getEnrichedEnv() {
|
|
12
25
|
const home = homedir();
|
|
@@ -22,7 +35,7 @@ function getEnrichedEnv() {
|
|
|
22
35
|
'/usr/sbin',
|
|
23
36
|
'/sbin'
|
|
24
37
|
];
|
|
25
|
-
const nvmDir =
|
|
38
|
+
const nvmDir = getNvmDir();
|
|
26
39
|
if (existsSync(nvmDir)) {
|
|
27
40
|
try {
|
|
28
41
|
const versionsDir = join(nvmDir, 'versions', 'node');
|
|
@@ -86,9 +99,18 @@ async function runNvm(script, timeout = 5000) {
|
|
|
86
99
|
if (process.platform === 'win32') {
|
|
87
100
|
throw new Error('Unix NVM shell is not supported on Windows.');
|
|
88
101
|
}
|
|
89
|
-
|
|
102
|
+
const nvmDir = getNvmDir();
|
|
103
|
+
const nvmSh = join(nvmDir, 'nvm.sh');
|
|
104
|
+
const nvmLoad = existsSync(nvmSh) ? `. "${nvmSh}"; ` : '';
|
|
105
|
+
const fullScript = `${nvmLoad}${script}`;
|
|
106
|
+
for (const shell of ['bash', 'sh', 'zsh']) {
|
|
90
107
|
try {
|
|
91
|
-
const { stdout } = await execFileAsync(shell, ['-
|
|
108
|
+
const { stdout } = await execFileAsync(shell, ['-c', fullScript], {
|
|
109
|
+
timeout,
|
|
110
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
111
|
+
env: { ...getEnrichedEnv(), NVM_DIR: nvmDir },
|
|
112
|
+
windowsHide: false
|
|
113
|
+
});
|
|
92
114
|
return stdout.trim();
|
|
93
115
|
} catch {}
|
|
94
116
|
}
|
|
@@ -100,22 +122,90 @@ function parseNvmVersions(output) {
|
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
export async function readNvmConfiguration() {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const defaultLine = value('DEFAULT');
|
|
125
|
+
const nvmDir = getNvmDir();
|
|
126
|
+
const nvmSh = join(nvmDir, 'nvm.sh');
|
|
127
|
+
if (!existsSync(nvmSh)) {
|
|
107
128
|
return {
|
|
108
|
-
installed:
|
|
109
|
-
version:
|
|
110
|
-
currentVersion:
|
|
111
|
-
defaultVersion:
|
|
112
|
-
nvmDir:
|
|
113
|
-
nodeJsMirror:
|
|
114
|
-
installedVersions:
|
|
129
|
+
installed: false,
|
|
130
|
+
version: '',
|
|
131
|
+
currentVersion: '',
|
|
132
|
+
defaultVersion: '',
|
|
133
|
+
nvmDir: '',
|
|
134
|
+
nodeJsMirror: '',
|
|
135
|
+
installedVersions: []
|
|
115
136
|
};
|
|
116
|
-
} catch {
|
|
117
|
-
return { installed: false, version: '', currentVersion: '', defaultVersion: '', nvmDir: '', nodeJsMirror: '', installedVersions: [] };
|
|
118
137
|
}
|
|
138
|
+
|
|
139
|
+
let version = '';
|
|
140
|
+
try {
|
|
141
|
+
const pkgFile = join(nvmDir, 'package.json');
|
|
142
|
+
if (existsSync(pkgFile)) {
|
|
143
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
144
|
+
version = pkg.version || '';
|
|
145
|
+
}
|
|
146
|
+
} catch {}
|
|
147
|
+
|
|
148
|
+
const installedVersions = [];
|
|
149
|
+
const versionsDir = join(nvmDir, 'versions', 'node');
|
|
150
|
+
if (existsSync(versionsDir)) {
|
|
151
|
+
try {
|
|
152
|
+
for (const entry of readdirSync(versionsDir)) {
|
|
153
|
+
if (/^v?\d+/.test(entry)) {
|
|
154
|
+
installedVersions.push(entry.startsWith('v') ? entry : `v${entry}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
for (const entry of readdirSync(nvmDir)) {
|
|
161
|
+
if (/^v\d+\.\d+\.\d+$/.test(entry) && existsSync(join(nvmDir, entry, 'bin', 'node'))) {
|
|
162
|
+
if (!installedVersions.includes(entry)) installedVersions.push(entry);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {}
|
|
166
|
+
installedVersions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
|
|
167
|
+
|
|
168
|
+
let defaultVersion = '';
|
|
169
|
+
try {
|
|
170
|
+
const defaultAliasFile = join(nvmDir, 'alias', 'default');
|
|
171
|
+
if (existsSync(defaultAliasFile)) {
|
|
172
|
+
defaultVersion = readFileSync(defaultAliasFile, 'utf8').trim();
|
|
173
|
+
}
|
|
174
|
+
} catch {}
|
|
175
|
+
|
|
176
|
+
let currentVersion = process.version || '';
|
|
177
|
+
try {
|
|
178
|
+
const currentLink = join(nvmDir, 'current');
|
|
179
|
+
if (existsSync(currentLink)) {
|
|
180
|
+
const real = basename(realpathSync(currentLink));
|
|
181
|
+
if (/^v?\d+/.test(real)) {
|
|
182
|
+
currentVersion = real.startsWith('v') ? real : `v${real}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
|
|
187
|
+
let nodeJsMirror = process.env.NVM_NODEJS_ORG_MIRROR || '';
|
|
188
|
+
if (!nodeJsMirror) {
|
|
189
|
+
try {
|
|
190
|
+
const shellFile = basename(process.env.SHELL || '') === 'bash' ? '.bashrc' : '.zshrc';
|
|
191
|
+
const file = join(homedir(), shellFile);
|
|
192
|
+
if (existsSync(file)) {
|
|
193
|
+
const content = readFileSync(file, 'utf8');
|
|
194
|
+
const match = content.match(/export\s+NVM_NODEJS_ORG_MIRROR="([^"]*)"/);
|
|
195
|
+
if (match) nodeJsMirror = match[1];
|
|
196
|
+
}
|
|
197
|
+
} catch {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
installed: true,
|
|
202
|
+
version,
|
|
203
|
+
currentVersion,
|
|
204
|
+
defaultVersion,
|
|
205
|
+
nvmDir,
|
|
206
|
+
nodeJsMirror,
|
|
207
|
+
installedVersions
|
|
208
|
+
};
|
|
119
209
|
}
|
|
120
210
|
|
|
121
211
|
async function readNpmConfig(key) {
|