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.
- package/README.md +32 -16
- package/dist/explode/transform.js +3 -4
- package/dist/explode/transform.js.map +1 -1
- package/dist/explode/unescapeMongo.js +33 -0
- package/dist/explode/unescapeMongo.js.map +1 -1
- package/dist/generated/operations.d.ts +23 -1
- package/dist/generated/schema.d.ts +16 -0
- package/dist/generated/schema.js.map +1 -1
- package/dist/import/index.d.ts +3 -1
- package/dist/import/index.js +27 -6
- package/dist/import/index.js.map +1 -1
- package/dist/include/index.d.ts +3 -1
- package/dist/include/index.js +129 -11
- package/dist/include/index.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.js +5 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/list.d.ts +2 -0
- package/dist/integrations/list.js +30 -16
- package/dist/integrations/list.js.map +1 -1
- package/dist/push/index.js +262 -42
- package/dist/push/index.js.map +1 -1
- package/dist/remove/index.d.ts +5 -0
- package/dist/remove/index.js +71 -0
- package/dist/remove/index.js.map +1 -0
- package/dist/rename/index.js +4 -3
- package/dist/rename/index.js.map +1 -1
- package/dist/run/index.d.ts +1 -1
- package/dist/utils/detectPullConflicts.js +13 -3
- package/dist/utils/detectPullConflicts.js.map +1 -1
- package/dist/utils/elementFiles.d.ts +8 -0
- package/dist/utils/elementFiles.js +33 -0
- package/dist/utils/elementFiles.js.map +1 -1
- package/dist/utils/graphqlError.js +16 -2
- package/dist/utils/graphqlError.js.map +1 -1
- package/dist/utils/hashContent.d.ts +1 -0
- package/dist/utils/hashContent.js +6 -3
- package/dist/utils/hashContent.js.map +1 -1
- package/dist/utils/integrationConfig.d.ts +13 -0
- package/dist/utils/integrationConfig.js +29 -0
- package/dist/utils/integrationConfig.js.map +1 -0
- package/dist/utils/mergeEndpointLists.d.ts +7 -0
- package/dist/utils/mergeEndpointLists.js +32 -0
- package/dist/utils/mergeEndpointLists.js.map +1 -0
- package/dist/utils/promptForEndpoints.d.ts +5 -0
- package/dist/utils/promptForEndpoints.js +37 -0
- package/dist/utils/promptForEndpoints.js.map +1 -0
- package/dist/utils/promptForReplaceLocal.d.ts +5 -0
- package/dist/utils/promptForReplaceLocal.js +18 -0
- package/dist/utils/promptForReplaceLocal.js.map +1 -0
- package/dist/utils/promptForValue.d.ts +2 -0
- package/dist/utils/promptForValue.js +6 -3
- package/dist/utils/promptForValue.js.map +1 -1
- package/dist/utils/resolveElementIncludes.d.ts +6 -0
- package/dist/utils/resolveElementIncludes.js +113 -0
- package/dist/utils/resolveElementIncludes.js.map +1 -0
- package/dist/utils/saveIntegration.js +7 -2
- package/dist/utils/saveIntegration.js.map +1 -1
- package/dist/utils/serviceFile.d.ts +3 -0
- package/dist/utils/serviceFile.js +7 -1
- package/dist/utils/serviceFile.js.map +1 -1
- package/dist/utils/writeShadowElement.d.ts +10 -7
- package/dist/utils/writeShadowElement.js +89 -50
- package/dist/utils/writeShadowElement.js.map +1 -1
- package/package.json +7 -7
- package/dist/utils/resolveElementInclude.d.ts +0 -3
- package/dist/utils/resolveElementInclude.js +0 -27
- package/dist/utils/resolveElementInclude.js.map +0 -1
package/dist/push/index.js
CHANGED
|
@@ -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 (
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
742
|
-
|
|
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(
|
|
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);
|