great 1.3.0 → 1.4.1

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 (69) hide show
  1. package/README.md +32 -16
  2. package/dist/explode/transform.js +3 -4
  3. package/dist/explode/transform.js.map +1 -1
  4. package/dist/explode/unescapeMongo.js +33 -0
  5. package/dist/explode/unescapeMongo.js.map +1 -1
  6. package/dist/generated/operations.d.ts +23 -1
  7. package/dist/generated/schema.d.ts +16 -0
  8. package/dist/generated/schema.js.map +1 -1
  9. package/dist/import/index.d.ts +3 -1
  10. package/dist/import/index.js +27 -6
  11. package/dist/import/index.js.map +1 -1
  12. package/dist/include/index.d.ts +3 -1
  13. package/dist/include/index.js +129 -11
  14. package/dist/include/index.js.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/integrations/index.js +5 -0
  18. package/dist/integrations/index.js.map +1 -1
  19. package/dist/integrations/list.d.ts +2 -0
  20. package/dist/integrations/list.js +30 -16
  21. package/dist/integrations/list.js.map +1 -1
  22. package/dist/push/index.js +262 -42
  23. package/dist/push/index.js.map +1 -1
  24. package/dist/remove/index.d.ts +5 -0
  25. package/dist/remove/index.js +71 -0
  26. package/dist/remove/index.js.map +1 -0
  27. package/dist/rename/index.js +4 -3
  28. package/dist/rename/index.js.map +1 -1
  29. package/dist/run/index.d.ts +1 -1
  30. package/dist/utils/detectPullConflicts.js +13 -3
  31. package/dist/utils/detectPullConflicts.js.map +1 -1
  32. package/dist/utils/elementFiles.d.ts +8 -0
  33. package/dist/utils/elementFiles.js +33 -0
  34. package/dist/utils/elementFiles.js.map +1 -1
  35. package/dist/utils/graphqlError.js +16 -2
  36. package/dist/utils/graphqlError.js.map +1 -1
  37. package/dist/utils/hashContent.d.ts +1 -0
  38. package/dist/utils/hashContent.js +6 -3
  39. package/dist/utils/hashContent.js.map +1 -1
  40. package/dist/utils/integrationConfig.d.ts +13 -0
  41. package/dist/utils/integrationConfig.js +29 -0
  42. package/dist/utils/integrationConfig.js.map +1 -0
  43. package/dist/utils/mergeEndpointLists.d.ts +7 -0
  44. package/dist/utils/mergeEndpointLists.js +32 -0
  45. package/dist/utils/mergeEndpointLists.js.map +1 -0
  46. package/dist/utils/promptForEndpoints.d.ts +5 -0
  47. package/dist/utils/promptForEndpoints.js +37 -0
  48. package/dist/utils/promptForEndpoints.js.map +1 -0
  49. package/dist/utils/promptForReplaceLocal.d.ts +5 -0
  50. package/dist/utils/promptForReplaceLocal.js +18 -0
  51. package/dist/utils/promptForReplaceLocal.js.map +1 -0
  52. package/dist/utils/promptForValue.d.ts +2 -0
  53. package/dist/utils/promptForValue.js +6 -3
  54. package/dist/utils/promptForValue.js.map +1 -1
  55. package/dist/utils/resolveElementIncludes.d.ts +6 -0
  56. package/dist/utils/resolveElementIncludes.js +113 -0
  57. package/dist/utils/resolveElementIncludes.js.map +1 -0
  58. package/dist/utils/saveIntegration.js +7 -2
  59. package/dist/utils/saveIntegration.js.map +1 -1
  60. package/dist/utils/serviceFile.d.ts +3 -0
  61. package/dist/utils/serviceFile.js +7 -1
  62. package/dist/utils/serviceFile.js.map +1 -1
  63. package/dist/utils/writeShadowElement.d.ts +10 -7
  64. package/dist/utils/writeShadowElement.js +89 -50
  65. package/dist/utils/writeShadowElement.js.map +1 -1
  66. package/package.json +7 -7
  67. package/dist/utils/resolveElementInclude.d.ts +0 -3
  68. package/dist/utils/resolveElementInclude.js +0 -27
  69. package/dist/utils/resolveElementInclude.js.map +0 -1
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import chalk from 'chalk';
3
3
  import { sentenceCase } from 'change-case';
4
4
  import { fetchIntegrationFromFile } from '../utils/fetchLocal.js';
5
- import { readGreatFile, writeGreatFile, getElementRecord, setElementRecord, removeElementRecordById, sameProvenance, } from '../utils/greatFile.js';
5
+ import { readGreatFile, writeGreatFile, getElementRecord, setElementRecord, removeElementRecord, removeElementRecordById, sameProvenance, } from '../utils/greatFile.js';
6
6
  import { readWorkspaceElements, writeElementFile, folderToElementType, } from '../utils/elementFiles.js';
7
7
  import { ENDPOINT_LISTS_FOLDER, validateEndpointReferences, } from '../utils/serviceFile.js';
8
8
  import { promptForDetach } from '../utils/promptForDetach.js';
@@ -14,8 +14,9 @@ import { getGraphQLErrorMessage, handleGraphQLError, isNotFoundError, } from '..
14
14
  import hashContent from '../utils/hashContent.js';
15
15
  import { VALID_VALUE_TYPES, isValidValueElementType, } from '../utils/valueTypes.js';
16
16
  import { setValueForElement } from '../values/set.js';
17
- import { promptForValueSetting } from '../utils/promptForValue.js';
17
+ import { promptForValueSetting, hasValueToSet, } from '../utils/promptForValue.js';
18
18
  import resolveIntegrationType from '../utils/integrationType.js';
19
+ import { CONFIG_KEY, CONFIG_RECORD_FOLDER, buildConfigRecord, findConfigElement, flagsFromIntegration, hashFlags, } from '../utils/integrationConfig.js';
19
20
  import { ElementType } from '../generated/schema.js';
20
21
  import { gql } from 'graphql-tag';
21
22
  const IntegrationForPushQueryDocument = gql `
@@ -44,6 +45,7 @@ const IntegrationForPushQueryDocument = gql `
44
45
  content {
45
46
  checksum
46
47
  createdAt
48
+ sourceId
47
49
  sourceTimestamp
48
50
  provenance
49
51
  }
@@ -136,8 +138,8 @@ const UpdateElementMutationDocument = gql `
136
138
  }
137
139
  `;
138
140
  const SetElementContentMutationDocument = gql `
139
- mutation SetElementContent($id: ID!, $content: JSON) {
140
- setElementContent(id: $id, content: $content) {
141
+ mutation SetElementContent($id: ID!, $content: JSON, $provenance: [ID!]) {
142
+ setElementContent(id: $id, content: $content, provenance: $provenance) {
141
143
  id
142
144
  content {
143
145
  createdAt
@@ -168,6 +170,7 @@ function compareShadow(local, remote, source) {
168
170
  const isList = isEndpointList(local);
169
171
  const hasNameChanged = !isList && localName !== (remote.name || '');
170
172
  const hasDescriptionChanged = !isList && localDesc !== (remote.description || '');
173
+ const hasSourceChanged = source.id !== remote.sourceId;
171
174
  const hasPinChanged = (source.timestamp ?? null) !== (remote.sourceTimestamp ?? null);
172
175
  const hasProvenanceChanged = !sameProvenance(local.provenance, remote.provenance);
173
176
  const hasRemoteMoved = local.timestamp !== remote.contentCreatedAt;
@@ -181,11 +184,18 @@ function compareShadow(local, remote, source) {
181
184
  }
182
185
  if (!hasNameChanged &&
183
186
  !hasDescriptionChanged &&
187
+ !hasSourceChanged &&
184
188
  !hasPinChanged &&
185
189
  !hasProvenanceChanged) {
186
190
  return { category: 'unchanged' };
187
191
  }
188
- if (hasPinChanged && hasRemoteMoved) {
192
+ if (hasSourceChanged && hasRemoteMoved) {
193
+ return {
194
+ category: 'conflict',
195
+ reason: ' Shadow source changed both local and remote',
196
+ };
197
+ }
198
+ else if (hasPinChanged && hasRemoteMoved) {
189
199
  return {
190
200
  category: 'conflict',
191
201
  reason: ' Shadow pin changed both local and remote',
@@ -214,7 +224,9 @@ function compareLocalAndRemote(local, remote) {
214
224
  const remoteCreatedAt = remote.createdAt
215
225
  ? new Date(remote.createdAt)
216
226
  : undefined;
217
- const hasContentChanged = localChecksum !== remoteChecksum;
227
+ const hasContentChanged = localChecksum !== remoteChecksum ||
228
+ Boolean(remote.sourceId) ||
229
+ !sameProvenance(local.provenance, remote.provenance);
218
230
  const hasNameChanged = !isList && localName !== remoteName;
219
231
  const hasDescriptionChanged = !isList && localDesc !== remoteDesc;
220
232
  if (!hasContentChanged && !hasNameChanged && !hasDescriptionChanged) {
@@ -246,6 +258,7 @@ function categorizeElements(localElements, remoteElementsMap) {
246
258
  const toInclude = [];
247
259
  const toUpdate = [];
248
260
  const toDetach = [];
261
+ const toSetValue = [];
249
262
  const toDelete = [];
250
263
  const conflicts = [];
251
264
  const localElementIds = new Set();
@@ -271,6 +284,9 @@ function categorizeElements(localElements, remoteElementsMap) {
271
284
  else if (result.category === 'detach') {
272
285
  toDetach.push({ ...local, remoteId: remote?.id });
273
286
  }
287
+ else if (hasValueToSet(local)) {
288
+ toSetValue.push({ ...local, remoteId: remote?.id });
289
+ }
274
290
  }
275
291
  for (const [id, remoteLement] of remoteElementsMap) {
276
292
  if (!localElementIds.has(id)) {
@@ -279,7 +295,15 @@ function categorizeElements(localElements, remoteElementsMap) {
279
295
  }
280
296
  toDelete.sort((a, b) => Number(b.folder === ENDPOINT_LISTS_FOLDER) -
281
297
  Number(a.folder === ENDPOINT_LISTS_FOLDER));
282
- return { toCreate, toInclude, toUpdate, toDetach, toDelete, conflicts };
298
+ return {
299
+ toCreate,
300
+ toInclude,
301
+ toUpdate,
302
+ toDetach,
303
+ toSetValue,
304
+ toDelete,
305
+ conflicts,
306
+ };
283
307
  }
284
308
  const prepareContentForApi = ({ name, description, ...content }, elementType) => {
285
309
  if (elementType === ElementType.Value) {
@@ -332,8 +356,9 @@ function buildShadowData(element, name, description, data) {
332
356
  ...data,
333
357
  };
334
358
  }
335
- function writeShadowFileAndRecord(fsImpl, element, great, source, included) {
359
+ function writeShadowFileAndRecord(fsImpl, element, great, source, included, keepValue = false) {
336
360
  const content = included.content;
361
+ const { value, ref } = element.data;
337
362
  setElementRecord(great, element.folder, element.key, {
338
363
  id: included.id,
339
364
  ...(content?.createdAt ? { timestamp: content.createdAt } : {}),
@@ -343,9 +368,13 @@ function writeShadowFileAndRecord(fsImpl, element, great, source, included) {
343
368
  ? { provenance: element.provenance }
344
369
  : {}),
345
370
  });
346
- writeElementFile(undefined, element.folder, element.key, buildShadowData(element, included.name, included.description, content?.data), great, fsImpl, element.filePath);
371
+ writeElementFile(undefined, element.folder, element.key, {
372
+ ...buildShadowData(element, included.name, included.description, content?.data),
373
+ ...(keepValue && value !== undefined ? { value } : {}),
374
+ ...(keepValue && ref !== undefined ? { ref } : {}),
375
+ }, great, fsImpl, element.filePath);
347
376
  }
348
- async function includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great) {
377
+ async function includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great, shouldSetValue) {
349
378
  const source = element.source;
350
379
  if (!source) {
351
380
  return false;
@@ -378,19 +407,67 @@ async function includeShadowElement(element, graphqlClient, integrationId, fsImp
378
407
  log(chalk.yellow(`Warning: Failed to update metadata for shadow ${element.key} in ${element.folder}`));
379
408
  }
380
409
  }
381
- writeShadowFileAndRecord(fsImpl, element, great, source, included);
410
+ const setsValue = shouldSetValue && hasValueToSet(element);
411
+ if (setsValue) {
412
+ recordElementProgress(great, element, { id: included.id });
413
+ const success = await setValueOrRef(graphqlClient, included.id, element, { type: included.content?.data?.type }, log);
414
+ if (!success) {
415
+ return false;
416
+ }
417
+ }
418
+ writeShadowFileAndRecord(fsImpl, element, great, source, included, !setsValue);
382
419
  return true;
383
420
  }
384
- async function updateElementContent(client, elementId, content) {
421
+ async function updateElementContent(client, elementId, content, provenance) {
385
422
  const result = await client.request(SetElementContentMutationDocument, {
386
423
  id: elementId,
387
424
  content,
425
+ ...(provenance && provenance.length > 0 ? { provenance } : {}),
388
426
  });
389
427
  if (result.error) {
390
428
  return undefined;
391
429
  }
392
430
  return result.data?.setElementContent?.content?.createdAt;
393
431
  }
432
+ async function setSecretRefForElement(client, elementId, element, log) {
433
+ const result = await client.request(SetSecretRefMutationDocument, { id: elementId, secretRef: String(element.data.ref) });
434
+ if (result.error) {
435
+ log(chalk.red(`Failed to set secret ref for ${element.key}: ${getGraphQLErrorMessage(result.error)}`));
436
+ return false;
437
+ }
438
+ log(chalk.green(`✓ Secret ref set for element "${element.key}"`));
439
+ return true;
440
+ }
441
+ async function setValueOrRef(client, id, element, content, log) {
442
+ const hasValue = element.data.value !== undefined;
443
+ const hasRef = element.data.ref !== undefined;
444
+ const isSecret = content.type === 'secret';
445
+ if (isSecret && hasRef && !hasValue) {
446
+ const success = await setSecretRefForElement(client, id, element, log);
447
+ if (!success) {
448
+ return false;
449
+ }
450
+ }
451
+ else if (hasValue) {
452
+ const valueStr = String(element.data.value);
453
+ const result = await setValueForElement(id, valueStr, content, client);
454
+ if (!result.success) {
455
+ log(chalk.red(`Failed to set value for ${element.key}: ${result.error}`));
456
+ return false;
457
+ }
458
+ log(chalk.green(`✓ Value set for element "${element.key}" (${content.type})`));
459
+ }
460
+ return true;
461
+ }
462
+ async function setValueOnly(element, id, graphqlClient, fsImpl, great, log) {
463
+ const success = await setValueOrRef(graphqlClient, id, element, { type: element.data.type }, log);
464
+ if (!success) {
465
+ return false;
466
+ }
467
+ const { value, ref, ...data } = element.data;
468
+ writeElementFile(undefined, element.folder, element.key, data, great, fsImpl, element.filePath);
469
+ return true;
470
+ }
394
471
  function updateLocalFile(fsImpl, element, great, updatedId, updatedTimestamp, generatedName, keepValue = false) {
395
472
  const { name, description, value, ref, ...data } = element.data;
396
473
  const existing = getElementRecord(great, element.folder, element.key);
@@ -408,6 +485,9 @@ function updateLocalFile(fsImpl, element, great, updatedId, updatedTimestamp, ge
408
485
  ...(id ? { id } : {}),
409
486
  ...(timestamp ? { timestamp } : {}),
410
487
  hash: hashContent(orderedData),
488
+ ...(element.provenance && element.provenance.length > 0
489
+ ? { provenance: element.provenance }
490
+ : {}),
411
491
  });
412
492
  writeElementFile(undefined, element.folder, element.key, orderedData, great, fsImpl, element.filePath);
413
493
  }
@@ -446,6 +526,7 @@ function buildRemoteElementsMap(elements) {
446
526
  name: element.name,
447
527
  description: element.description,
448
528
  createdAt: element.createdAt,
529
+ sourceId: element.content?.sourceId,
449
530
  sourceTimestamp: element.content?.sourceTimestamp,
450
531
  provenance: element.content?.provenance,
451
532
  });
@@ -496,10 +577,14 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
496
577
  }
497
578
  if (element.source && !element.detach) {
498
579
  const remote = apiElementsMap.get(id);
580
+ const sourceChanged = element.source.id !== remote?.sourceId;
499
581
  const pinChanged = (element.source.timestamp ?? null) !== (remote?.sourceTimestamp ?? null);
500
582
  const provenanceChanged = !sameProvenance(element.provenance, remote?.provenance);
501
- if (pinChanged || provenanceChanged) {
502
- return includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great);
583
+ if (sourceChanged || pinChanged || provenanceChanged) {
584
+ return includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great, shouldSetValue);
585
+ }
586
+ if (shouldSetValue && hasValueToSet(element)) {
587
+ return setValueOnly(element, id, graphqlClient, fsImpl, great, log);
503
588
  }
504
589
  return true;
505
590
  }
@@ -512,7 +597,7 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
512
597
  log(chalk.red(`Failed to prepare content for ${element.key} in ${element.folder}: ${error instanceof Error ? error.message : String(error)}`));
513
598
  return false;
514
599
  }
515
- const timestamp = await updateElementContent(graphqlClient, id, content);
600
+ const timestamp = await updateElementContent(graphqlClient, id, content, element.provenance);
516
601
  if (!timestamp) {
517
602
  log(chalk.red(`Failed to ${element.remoteId ? 'update' : 'set'} content for ${element.key} in ${element.folder}`));
518
603
  return false;
@@ -520,28 +605,13 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
520
605
  recordElementProgress(great, element, { id, timestamp });
521
606
  const hasValue = element.data.value !== undefined;
522
607
  const hasRef = element.data.ref !== undefined;
523
- const isSecret = content.type === 'secret';
524
608
  if (elementType === ElementType.Value && shouldSetValue) {
525
- if (isSecret && hasRef && !hasValue) {
526
- const secretRef = String(element.data.ref);
527
- const result = await graphqlClient.request(SetSecretRefMutationDocument, { id, secretRef });
528
- if (result.error) {
529
- log(chalk.red(`Failed to set secret ref for ${element.key}: ${getGraphQLErrorMessage(result.error)}`));
530
- return false;
531
- }
532
- log(chalk.green(`✓ Secret ref set for element "${element.key}"`));
533
- }
534
- else if (hasValue) {
535
- const valueStr = String(element.data.value);
536
- const result = await setValueForElement(id, valueStr, content, graphqlClient);
537
- if (!result.success) {
538
- log(chalk.red(`Failed to set value for ${element.key}: ${result.error}`));
539
- return false;
540
- }
541
- log(chalk.green(`✓ Value set for element "${element.key}" (${content.type})`));
609
+ const success = await setValueOrRef(graphqlClient, id, element, content, log);
610
+ if (!success) {
611
+ return false;
542
612
  }
543
613
  }
544
- updateLocalFile(fsImpl, element, great, element.remoteId ? undefined : id, timestamp, generatedName, !shouldSetValue && hasValue);
614
+ updateLocalFile(fsImpl, element, great, element.remoteId ? undefined : id, timestamp, generatedName, !shouldSetValue && (hasValue || hasRef));
545
615
  return true;
546
616
  }
547
617
  async function deleteElement(element, graphqlClient, log) {
@@ -565,7 +635,7 @@ async function resolveDetaches(toDetach, promptForDetachFn, graphqlClient, fsImp
565
635
  return undefined;
566
636
  }
567
637
  if (shouldDetach) {
568
- detached.push({ ...element, detach: true });
638
+ detached.push({ ...element, detach: true, provenance: undefined });
569
639
  }
570
640
  else {
571
641
  await restoreShadowFile(element, graphqlClient, fsImpl, great, log);
@@ -634,6 +704,110 @@ async function syncIntegrationType(graphqlClient, integrationId, remoteType, loc
634
704
  }
635
705
  log(`${chalk.green('✓')} Updated integration type to ${chalk.cyan(localType)}`);
636
706
  }
707
+ function buildRemoteConfig(elements) {
708
+ const config = findConfigElement(elements);
709
+ return config
710
+ ? {
711
+ id: config.id,
712
+ key: CONFIG_KEY,
713
+ folder: CONFIG_RECORD_FOLDER,
714
+ checksum: config.content?.checksum,
715
+ contentCreatedAt: config.content?.createdAt,
716
+ }
717
+ : undefined;
718
+ }
719
+ function categorizeConfig(flags, remote, record) {
720
+ if (!remote) {
721
+ if (!flags) {
722
+ return { action: 'unchanged' };
723
+ }
724
+ return record?.id
725
+ ? {
726
+ action: 'conflict',
727
+ reason: 'Flags exist locally with an id but not found in Integreat',
728
+ }
729
+ : { action: 'create' };
730
+ }
731
+ const remoteChanged = (record?.timestamp ?? null) !== (remote.contentCreatedAt ?? null);
732
+ if (!flags) {
733
+ if (!record) {
734
+ return { action: 'unchanged' };
735
+ }
736
+ return remoteChanged
737
+ ? {
738
+ action: 'conflict',
739
+ reason: 'Flags removed locally but changed in Integreat',
740
+ }
741
+ : { action: 'delete' };
742
+ }
743
+ if (hashFlags(flags) === remote.checksum) {
744
+ return { action: 'unchanged' };
745
+ }
746
+ if (!remoteChanged) {
747
+ return { action: 'update' };
748
+ }
749
+ return {
750
+ action: 'conflict',
751
+ reason: record
752
+ ? 'Flags changed both local and remote'
753
+ : 'Flags differ from Integreat and have not been synced before',
754
+ };
755
+ }
756
+ const isConfigChange = ({ action }) => action === 'create' || action === 'update' || action === 'delete';
757
+ function refreshConfigRecord(great, flags, remote) {
758
+ const existing = getElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY);
759
+ if (flags && remote) {
760
+ const record = buildConfigRecord(remote.id, remote.contentCreatedAt, flags);
761
+ setElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY, record);
762
+ return JSON.stringify(record) !== JSON.stringify(existing);
763
+ }
764
+ if (!flags && !remote && existing) {
765
+ removeElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY);
766
+ return true;
767
+ }
768
+ return false;
769
+ }
770
+ async function createConfigElement(graphqlClient, integrationId, great) {
771
+ const result = await graphqlClient.request(CreateElementMutationDocument, {
772
+ integrationId,
773
+ key: CONFIG_KEY,
774
+ type: ElementType.Config,
775
+ name: sentenceCase(CONFIG_KEY),
776
+ });
777
+ const id = result.error ? undefined : result.data?.createElement?.id;
778
+ if (id) {
779
+ setElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY, { id });
780
+ }
781
+ return id;
782
+ }
783
+ async function setConfigContent(graphqlClient, id, flags, great) {
784
+ const timestamp = await updateElementContent(graphqlClient, id, flags, undefined);
785
+ if (!timestamp) {
786
+ return false;
787
+ }
788
+ setElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY, buildConfigRecord(id, timestamp, flags));
789
+ return true;
790
+ }
791
+ async function pushConfig(change, flags, remote, graphqlClient, integrationId, great, log) {
792
+ if (change.action === 'delete' && remote) {
793
+ const success = await deleteElement(remote, graphqlClient, log);
794
+ if (success) {
795
+ removeElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY);
796
+ log(chalk.green('✓ Deleted flags'));
797
+ }
798
+ return success;
799
+ }
800
+ const id = change.action === 'create'
801
+ ? await createConfigElement(graphqlClient, integrationId, great)
802
+ : remote?.id;
803
+ const success = !!id && !!flags && (await setConfigContent(graphqlClient, id, flags, great));
804
+ if (!success) {
805
+ log(chalk.red(`Failed to ${change.action} flags`));
806
+ return false;
807
+ }
808
+ log(chalk.green(`✓ ${change.action === 'create' ? 'Created' : 'Updated'} flags`));
809
+ return true;
810
+ }
637
811
  function logSummary(createdCount, includedCount, updatedCount, deletedCount, registeredCount, log) {
638
812
  const total = createdCount + includedCount + updatedCount + deletedCount + registeredCount;
639
813
  if (total > 0) {
@@ -737,9 +911,17 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
737
911
  for (const warning of validateEndpointReferences(localElements, great)) {
738
912
  log(chalk.yellow(`Warning: ${warning}`));
739
913
  }
740
- const { toCreate, toInclude, toUpdate, toDetach, toDelete, conflicts } = categorizeElements(localElements, apiElementsMap);
741
- if (conflicts.length > 0) {
742
- handleConflicts(conflicts, log);
914
+ const { toCreate, toInclude, toUpdate, toDetach, toSetValue, toDelete, conflicts, } = categorizeElements(localElements, apiElementsMap);
915
+ const localFlags = flagsFromIntegration(localData);
916
+ const remoteConfig = buildRemoteConfig(integration.latest.elements);
917
+ const configChange = categorizeConfig(localFlags, remoteConfig, getElementRecord(great, CONFIG_RECORD_FOLDER, CONFIG_KEY));
918
+ if (conflicts.length > 0 || configChange.action === 'conflict') {
919
+ handleConflicts(configChange.action === 'conflict'
920
+ ? [
921
+ ...conflicts,
922
+ { filePath: 'integration.json', reason: configChange.reason },
923
+ ]
924
+ : conflicts, log);
743
925
  return;
744
926
  }
745
927
  const detached = await resolveDetaches(toDetach, promptForDetachFn, graphqlClient, fsImpl, great, log);
@@ -753,15 +935,20 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
753
935
  if (toCreate.length +
754
936
  toInclude.length +
755
937
  toUpdate.length +
938
+ toSetValue.length +
756
939
  toDelete.length +
757
- importsToRegister.length ===
940
+ importsToRegister.length +
941
+ Number(isConfigChange(configChange)) ===
758
942
  0) {
943
+ if (refreshConfigRecord(great, localFlags, remoteConfig)) {
944
+ writeGreatFile(great, undefined, fsImpl);
945
+ }
759
946
  log(chalk.green('No changes to push'));
760
947
  return;
761
948
  }
762
949
  let shouldSetValue;
763
950
  try {
764
- shouldSetValue = await promptForValueSettingFn([...toCreate, ...toUpdate], log);
951
+ shouldSetValue = await promptForValueSettingFn([...toCreate, ...toInclude, ...toUpdate, ...toSetValue], log);
765
952
  }
766
953
  catch {
767
954
  log(chalk.yellow('\nPush cancelled'));
@@ -780,7 +967,7 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
780
967
  }
781
968
  let includedCount = 0;
782
969
  for (const element of toInclude) {
783
- const success = await includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great);
970
+ const success = await includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great, shouldSetValue);
784
971
  if (!success) {
785
972
  persistProgress();
786
973
  return;
@@ -798,6 +985,20 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
798
985
  log(chalk.green(`✓ Updated ${element.folder}/${element.key}`));
799
986
  updatedCount++;
800
987
  }
988
+ if (shouldSetValue) {
989
+ for (const element of toSetValue) {
990
+ if (!element.remoteId) {
991
+ continue;
992
+ }
993
+ const success = await setValueOnly(element, element.remoteId, graphqlClient, fsImpl, great, log);
994
+ if (!success) {
995
+ persistProgress();
996
+ return;
997
+ }
998
+ log(chalk.green(`✓ Updated ${element.folder}/${element.key}`));
999
+ updatedCount++;
1000
+ }
1001
+ }
801
1002
  let deletedCount = 0;
802
1003
  for (const element of toDelete) {
803
1004
  const success = await deleteElement(element, graphqlClient, log);
@@ -812,12 +1013,31 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
812
1013
  log(chalk.green(`✓ Deleted ${label}`));
813
1014
  deletedCount++;
814
1015
  }
1016
+ if (isConfigChange(configChange)) {
1017
+ const success = await pushConfig(configChange, localFlags, remoteConfig, graphqlClient, integrationId, great, log);
1018
+ if (!success) {
1019
+ persistProgress();
1020
+ return;
1021
+ }
1022
+ if (configChange.action === 'create')
1023
+ createdCount++;
1024
+ if (configChange.action === 'update')
1025
+ updatedCount++;
1026
+ if (configChange.action === 'delete')
1027
+ deletedCount++;
1028
+ }
1029
+ else {
1030
+ refreshConfigRecord(great, localFlags, remoteConfig);
1031
+ }
815
1032
  const registeredCount = await registerTemplateImports(importsToRegister, integration.templateImports ?? [], graphqlClient, integrationId, great, log);
816
1033
  if (registeredCount === undefined) {
817
1034
  persistProgress();
818
1035
  return;
819
1036
  }
820
- const localKeys = new Set(localElements.map((el) => `${el.folder}/${el.key}`));
1037
+ const localKeys = new Set([
1038
+ ...localElements.map((el) => `${el.folder}/${el.key}`),
1039
+ `${CONFIG_RECORD_FOLDER}/${CONFIG_KEY}`,
1040
+ ]);
821
1041
  great.elements = Object.fromEntries(Object.entries(great.elements).filter(([key]) => localKeys.has(key)));
822
1042
  writeGreatFile(great, undefined, fsImpl);
823
1043
  logSummary(createdCount, includedCount, updatedCount, deletedCount, registeredCount, log);