@ibm-cloud/cd-tools 1.8.1 → 1.8.2

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.
@@ -207,6 +207,20 @@ class GitLabClient {
207
207
 
208
208
  return all;
209
209
  }
210
+
211
+ async getGroupByFullPath(fullPath) {
212
+ const encoded = encodeURIComponent(fullPath);
213
+ const resp = await this.client.get(`/groups/${encoded}`);
214
+ return resp.data;
215
+ }
216
+
217
+ async listBulkImports({ page = 1, perPage = 50 } = {}) {
218
+ const resp = await getWithRetry(this.client, `/bulk_imports`, { page, per_page: perPage });
219
+ return {
220
+ imports: resp.data || [],
221
+ nextPage: Number(resp.headers?.['x-next-page'] || 0),
222
+ };
223
+ }
210
224
  }
211
225
 
212
226
  async function promptUser(name) {
@@ -361,6 +375,84 @@ function formatBulkImportProgressLine(importStatus, summary) {
361
375
  return parts.join(' | ');
362
376
  }
363
377
 
378
+ function buildGroupUrl(base, path) {
379
+ try {
380
+ return new URL(path.replace(/^\//, ''), base).toString();
381
+ } catch {
382
+ return null;
383
+ }
384
+ }
385
+
386
+ function isGroupEntity(e) {
387
+ return e?.source_type === 'group_entity' || e?.entity_type === 'group_entity' || e?.entity_type === 'group';
388
+ }
389
+
390
+ async function handleBulkImportConflict({destination, destUrl, sourceGroupFullPath, destinationGroupPath, importResErr}) {
391
+ const historyUrl = buildGroupImportHistoryUrl(destUrl);
392
+ const groupUrl = buildGroupUrl(destUrl, `/groups/${destinationGroupPath}`);
393
+ const fallback = () => {
394
+ console.log(`\nDestination group already exists.`);
395
+ if (groupUrl) console.log(`Group: ${groupUrl}`);
396
+ if (historyUrl) console.log(`Group import history: ${historyUrl}`);
397
+ process.exit(0);
398
+ };
399
+
400
+ try {
401
+ await destination.getGroupByFullPath(destinationGroupPath);
402
+ } catch {
403
+ fallback();
404
+ }
405
+
406
+ try {
407
+ const IMPORT_PAGES = 3;
408
+ const ENTITY_PAGES = 2;
409
+
410
+ let page = 1;
411
+ for (let p = 0; p < IMPORT_PAGES; p++) {
412
+ const { imports, nextPage } = await destination.listBulkImports({ page, perPage: 50 });
413
+
414
+ for (const bi of imports) {
415
+ if (!bi?.id) continue;
416
+
417
+ const status = bi.status;
418
+ if (!['created', 'started', 'finished'].includes(status)) continue;
419
+
420
+ const entities = await destination.getBulkImportEntitiesAll(bi.id, { perPage: 100, maxPages: ENTITY_PAGES });
421
+
422
+ const matchesThisGroup = entities.some(e =>
423
+ isGroupEntity(e) &&
424
+ e.source_full_path === sourceGroupFullPath &&
425
+ (e.destination_full_path === destinationGroupPath || e.destination_slug === destinationGroupPath)
426
+ );
427
+
428
+ if (!matchesThisGroup) continue;
429
+
430
+ if (status === 'created' || status === 'started') {
431
+ console.log(`\nGroup is already in migration...`);
432
+ console.log(`Bulk import ID: ${bi.id}`);
433
+ if (groupUrl) console.log(`Migrated group: ${groupUrl}`);
434
+ if (historyUrl) console.log(`Group import history: ${historyUrl}`);
435
+ process.exit(0);
436
+ }
437
+
438
+ console.log(`\nConflict detected: ${importResErr}`);
439
+ console.log(`Please specify a new group name using -n, --new-name <n> when trying again`);
440
+ console.log(`\nGroup already migrated.`);
441
+ if (groupUrl) console.log(`Migrated group: ${groupUrl}`);
442
+ if (historyUrl) console.log(`Group import history: ${historyUrl}`);
443
+ process.exit(0);
444
+ }
445
+
446
+ if (!nextPage) break;
447
+ page = nextPage;
448
+ }
449
+
450
+ fallback();
451
+ } catch {
452
+ fallback();
453
+ }
454
+ }
455
+
364
456
  async function directTransfer(options) {
365
457
  const sourceUrl = validateAndConvertRegion(options.sourceRegion);
366
458
  const destUrl = validateAndConvertRegion(options.destRegion);
@@ -418,9 +510,13 @@ async function directTransfer(options) {
418
510
  console.log(`Bulk import request succeeded!`);
419
511
  console.log(`Bulk import initiated successfully (ID: ${importRes.data?.id})`);
420
512
  } else if (importRes.conflict) {
421
- console.log(`Conflict detected: ${importRes.error}`);
422
- console.log(`Please specify a new group name using -n, --new-name <n> when trying again`);
423
- process.exit(0);
513
+ await handleBulkImportConflict({
514
+ destination,
515
+ destUrl,
516
+ sourceGroupFullPath: sourceGroup.full_path,
517
+ destinationGroupPath,
518
+ importResErr: importRes.error
519
+ });
424
520
  }
425
521
  } catch (error) {
426
522
  console.log(`Bulk import request failed - ${error.message}`);
@@ -506,6 +602,8 @@ async function directTransfer(options) {
506
602
  console.log(`${e.source_type}: ${e.source_full_path} (${e.status})`);
507
603
  });
508
604
  }
605
+ const migratedGroupUrl = buildGroupUrl(destUrl, `/groups/${destinationGroupPath}`);
606
+ if (migratedGroupUrl) console.log(`\nMigrated group: ${migratedGroupUrl}`);
509
607
 
510
608
  return 0;
511
609
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibm-cloud/cd-tools",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Tools and utilities for the IBM Cloud Continuous Delivery service and resources",
5
5
  "repository": {
6
6
  "type": "git",