@@ -332,15 +332,18 @@ async function getFeatureFlag(apiKey, projectKey, flagKey) {
332332/**
333333 * Set a custom property on a feature flag
334334 */
335- async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue) {
335+ async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, propertyValue, hasExistingProperty = false ) {
336336 const url = `https://app.launchdarkly.com/api/v2/flags/${projectKey}/${flagKey}`;
337337
338+ // Use 'replace' if property exists, 'add' if it doesn't
339+ const operation = hasExistingProperty ? 'replace' : 'add';
340+
338341 // Prepare the JSON patch operation to set the custom property
339342 // Using standard JSON patch format with correct "values" field
340343 const patchData = {
341344 patch: [
342345 {
343- op: 'add' ,
346+ op: operation ,
344347 path: `/customProperties/${propertyName}`,
345348 value: {
346349 name: propertyName,
@@ -350,7 +353,7 @@ async function setCustomProperty(apiKey, projectKey, flagKey, propertyName, prop
350353 ]
351354 };
352355
353- core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey}`);
356+ core.debug(`Setting custom property ${propertyName} = ${propertyValue} on flag: ${flagKey} (operation: ${operation}) `);
354357 core.debug(`Request URL: ${url}`);
355358 core.debug(`Request body: ${JSON.stringify(patchData)}`);
356359
@@ -484,8 +487,14 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
484487 const expiryDateString = calculateExpiryFromCreation(flag, daysFromCreation, dateFormat);
485488 const creationDate = new Date(flag.creationDate);
486489
487- // Set the custom property
488- await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString);
490+ // Check if the custom property already exists
491+ const hasExistingProperty = flag.customProperties &&
492+ flag.customProperties[customPropertyName] &&
493+ flag.customProperties[customPropertyName].value &&
494+ flag.customProperties[customPropertyName].value.length > 0;
495+
496+ // Set the custom property (with appropriate operation)
497+ await setCustomProperty(apiKey, projectKey, flag.key, customPropertyName, expiryDateString, hasExistingProperty);
489498
490499 return {
491500 key: flag.key,
@@ -512,7 +521,7 @@ async function processSingleFlag(flag, apiKey, projectKey, customPropertyName, d
512521}
513522
514523/**
515- * Process flags in batches to avoid overwhelming the API
524+ * Process flags sequentially to avoid rate limiting
516525 */
517526async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat) {
518527 const results = {
@@ -526,60 +535,42 @@ async function processFlagsInBatches(flagsToProcess, apiKey, projectKey, customP
526535 return results;
527536 }
528537
529- // Split flags into batches
530- const batches = [];
531- for (let i = 0; i < flagsToProcess.length; i += BATCH_SIZE) {
532- batches.push(flagsToProcess.slice(i, i + BATCH_SIZE));
533- }
538+ core.info(`🚀 Processing ${flagsToProcess.length} flags sequentially`);
534539
535- core.info(`🚀 Processing ${flagsToProcess.length} flags in ${batches.length} batches of ${BATCH_SIZE}`);
536-
537- for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
538- const batch = batches[batchIndex];
539-
540- core.info(`📦 Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} flags)`);
540+ for (let i = 0; i < flagsToProcess.length; i++) {
541+ const flag = flagsToProcess[i];
542+ results.totalProcessed++;
541543
542- // Process batch in parallel
543- const batchPromises = batch.map(flag =>
544- processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat)
545- );
544+ core.info(`📦 Processing flag ${i + 1}/${flagsToProcess.length}: ${flag.key}`);
546545
547- const batchResults = await Promise.allSettled(batchPromises);
548-
549- // Collect results
550- batchResults.forEach((result, index) => {
551- const flag = batch[index];
552- results.totalProcessed++;
553-
554- if (result.status === 'fulfilled') {
555- results.updatedFlags.push(result.value);
556- core.info(` ✅ ${flag.key}: ${result.value.calculatedExpiryDate}`);
546+ try {
547+ const result = await processSingleFlag(flag, apiKey, projectKey, customPropertyName, daysFromCreation, dateFormat);
548+ results.updatedFlags.push(result);
549+ core.info(` ✅ ${flag.key}: ${result.calculatedExpiryDate}`);
550+ } catch (error) {
551+ // Better error handling - extract message safely
552+ let errorMessage;
553+ if (error instanceof Error) {
554+ errorMessage = error.message;
555+ } else if (typeof error === 'string') {
556+ errorMessage = error;
557+ } else if (error && error.message) {
558+ errorMessage = error.message;
557559 } else {
558- // Better error handling - extract message safely
559- let errorMessage;
560- if (result.reason instanceof Error) {
561- errorMessage = result.reason.message;
562- } else if (typeof result.reason === 'string') {
563- errorMessage = result.reason;
564- } else if (result.reason && result.reason.message) {
565- errorMessage = result.reason.message;
566- } else {
567- errorMessage = 'Unknown error occurred';
568- }
569-
570- results.failedFlags.push({
571- key: flag.key,
572- name: flag.name,
573- error: errorMessage
574- });
575- core.error(` ❌ ${flag.key}: ${errorMessage}`);
560+ errorMessage = 'Unknown error occurred';
576561 }
577- });
562+
563+ results.failedFlags.push({
564+ key: flag.key,
565+ name: flag.name,
566+ error: errorMessage
567+ });
568+ core.error(` ❌ ${flag.key}: ${errorMessage}`);
569+ }
578570
579- // Delay between batches (except last) to be respectful of rate limits
580- if (batchIndex < batches.length - 1) {
581- core.debug(`Waiting ${BATCH_DELAY}ms before next batch...`);
582- await sleep(BATCH_DELAY);
571+ // Add delay between each flag to respect rate limits
572+ if (i < flagsToProcess.length - 1) {
573+ await sleep(RATE_LIMIT_DELAY);
583574 }
584575 }
585576
0 commit comments