great 1.0.2 → 1.2.0
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 +141 -43
- package/dist/accounts/create.d.ts +1 -1
- package/dist/accounts/create.js +2 -4
- package/dist/accounts/create.js.map +1 -1
- package/dist/accounts/index.d.ts +1 -1
- package/dist/accounts/index.js +2 -4
- package/dist/accounts/index.js.map +1 -1
- package/dist/clone/index.d.ts +4 -2
- package/dist/clone/index.js +19 -43
- package/dist/clone/index.js.map +1 -1
- package/dist/config/index.js +4 -30
- package/dist/config/index.js.map +1 -1
- package/dist/deploy/index.d.ts +1 -1
- package/dist/deploy/index.js +55 -15
- package/dist/deploy/index.js.map +1 -1
- package/dist/env/index.d.ts +12 -0
- package/dist/env/index.js +131 -0
- package/dist/env/index.js.map +1 -0
- package/dist/explode/transform.d.ts +1 -1
- package/dist/explode/transform.js +6 -2
- package/dist/explode/transform.js.map +1 -1
- package/dist/explode/unescapeMongo.js +9 -1
- package/dist/explode/unescapeMongo.js.map +1 -1
- package/dist/generated/operations.d.ts +617 -0
- package/dist/generated/operations.js +2 -0
- package/dist/generated/operations.js.map +1 -0
- package/dist/generated/schema.d.ts +965 -0
- package/dist/generated/{graphql.js → schema.js} +20 -2
- package/dist/generated/schema.js.map +1 -0
- package/dist/implode/escapeMongo.d.ts +1 -0
- package/dist/implode/escapeMongo.js +23 -0
- package/dist/implode/escapeMongo.js.map +1 -0
- package/dist/implode/index.d.ts +6 -0
- package/dist/implode/index.js +132 -0
- package/dist/implode/index.js.map +1 -0
- package/dist/implode/transform.d.ts +45 -0
- package/dist/implode/transform.js +73 -0
- package/dist/implode/transform.js.map +1 -0
- package/dist/import/index.d.ts +8 -0
- package/dist/import/index.js +43 -0
- package/dist/import/index.js.map +1 -0
- package/dist/include/index.d.ts +10 -0
- package/dist/include/index.js +41 -0
- package/dist/include/index.js.map +1 -0
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/init/index.js +8 -16
- package/dist/init/index.js.map +1 -1
- package/dist/integrations/create.d.ts +1 -1
- package/dist/integrations/create.js +3 -5
- package/dist/integrations/create.js.map +1 -1
- package/dist/integrations/list.d.ts +1 -1
- package/dist/integrations/list.js +3 -5
- package/dist/integrations/list.js.map +1 -1
- package/dist/migrate/index.d.ts +5 -0
- package/dist/migrate/index.js +101 -0
- package/dist/migrate/index.js.map +1 -0
- package/dist/pull/index.d.ts +4 -2
- package/dist/pull/index.js +25 -44
- package/dist/pull/index.js.map +1 -1
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +364 -93
- package/dist/push/index.js.map +1 -1
- package/dist/rename/index.d.ts +8 -0
- package/dist/rename/index.js +109 -0
- package/dist/rename/index.js.map +1 -0
- package/dist/repl/dispatch.d.ts +8 -2
- package/dist/repl/dispatch.js +43 -29
- package/dist/repl/dispatch.js.map +1 -1
- package/dist/repl/dispatchAndLoad.d.ts +7 -0
- package/dist/repl/dispatchAndLoad.js +35 -0
- package/dist/repl/dispatchAndLoad.js.map +1 -0
- package/dist/repl/help.js +13 -2
- package/dist/repl/help.js.map +1 -1
- package/dist/repl/index.d.ts +1 -1
- package/dist/repl/index.js +31 -10
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/load.d.ts +8 -0
- package/dist/repl/load.js +53 -0
- package/dist/repl/load.js.map +1 -0
- package/dist/run/index.d.ts +7 -5
- package/dist/run/index.js +5 -7
- package/dist/run/index.js.map +1 -1
- package/dist/status/index.d.ts +1 -1
- package/dist/status/index.js +6 -8
- package/dist/status/index.js.map +1 -1
- package/dist/utils/config.d.ts +9 -2
- package/dist/utils/config.js +39 -6
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/createIntegrationFromFile.d.ts +3 -1
- package/dist/utils/createIntegrationFromFile.js +14 -30
- package/dist/utils/createIntegrationFromFile.js.map +1 -1
- package/dist/utils/detectPullConflicts.d.ts +8 -0
- package/dist/utils/detectPullConflicts.js +65 -0
- package/dist/utils/detectPullConflicts.js.map +1 -0
- package/dist/utils/dispatch.d.ts +2 -2
- package/dist/utils/dispatch.js +5 -4
- package/dist/utils/dispatch.js.map +1 -1
- package/dist/utils/elementFiles.d.ts +13 -0
- package/dist/utils/elementFiles.js +76 -0
- package/dist/utils/elementFiles.js.map +1 -0
- package/dist/utils/fetchIntegrationDefinition.d.ts +8 -0
- package/dist/utils/fetchIntegrationDefinition.js +58 -0
- package/dist/utils/fetchIntegrationDefinition.js.map +1 -0
- package/dist/utils/fetchLocal.d.ts +3 -1
- package/dist/utils/fetchLocal.js +11 -9
- package/dist/utils/fetchLocal.js.map +1 -1
- package/dist/utils/fetchSession.d.ts +1 -1
- package/dist/utils/fetchSession.js +2 -1
- package/dist/utils/fetchSession.js.map +1 -1
- package/dist/utils/graphqlClient.d.ts +3 -0
- package/dist/utils/graphqlClient.js +3 -0
- package/dist/utils/graphqlClient.js.map +1 -1
- package/dist/utils/graphqlError.d.ts +2 -1
- package/dist/utils/graphqlError.js +17 -3
- package/dist/utils/graphqlError.js.map +1 -1
- package/dist/utils/greatFile.d.ts +40 -0
- package/dist/utils/greatFile.js +66 -0
- package/dist/utils/greatFile.js.map +1 -0
- package/dist/utils/hashContent.js +1 -1
- package/dist/utils/hashContent.js.map +1 -1
- package/dist/utils/integrationType.d.ts +3 -0
- package/dist/utils/integrationType.js +15 -0
- package/dist/utils/integrationType.js.map +1 -0
- package/dist/utils/parseJson.d.ts +1 -0
- package/dist/utils/parseJson.js +5 -0
- package/dist/utils/parseJson.js.map +1 -0
- package/dist/utils/resolveAccount.d.ts +5 -4
- package/dist/utils/resolveAccount.js +17 -9
- package/dist/utils/resolveAccount.js.map +1 -1
- package/dist/utils/resolveElement.d.ts +1 -1
- package/dist/utils/resolveElementInclude.d.ts +3 -0
- package/dist/utils/resolveElementInclude.js +28 -0
- package/dist/utils/resolveElementInclude.js.map +1 -0
- package/dist/utils/resolveIntegration.d.ts +4 -2
- package/dist/utils/resolveIntegration.js +7 -3
- package/dist/utils/resolveIntegration.js.map +1 -1
- package/dist/utils/resolveTemplateImport.d.ts +3 -0
- package/dist/utils/resolveTemplateImport.js +31 -0
- package/dist/utils/resolveTemplateImport.js.map +1 -0
- package/dist/utils/response.d.ts +3 -0
- package/dist/utils/response.js +24 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/saveIntegration.d.ts +8 -4
- package/dist/utils/saveIntegration.js +76 -34
- package/dist/utils/saveIntegration.js.map +1 -1
- package/dist/utils/token.js +1 -1
- package/dist/utils/token.js.map +1 -1
- package/dist/utils/url.d.ts +1 -0
- package/dist/utils/url.js +10 -0
- package/dist/utils/url.js.map +1 -0
- package/dist/utils/writeShadowElement.d.ts +26 -0
- package/dist/utils/writeShadowElement.js +119 -0
- package/dist/utils/writeShadowElement.js.map +1 -0
- package/dist/{value → values}/get.d.ts +1 -1
- package/dist/{value → values}/get.js +7 -8
- package/dist/values/get.js.map +1 -0
- package/dist/{value → values}/index.js +13 -1
- package/dist/values/index.js.map +1 -0
- package/dist/{value → values}/list.js +10 -9
- package/dist/values/list.js.map +1 -0
- package/dist/{value → values}/set.d.ts +1 -1
- package/dist/{value → values}/set.js +9 -9
- package/dist/values/set.js.map +1 -0
- package/dist/values/unset.d.ts +8 -0
- package/dist/values/unset.js +61 -0
- package/dist/values/unset.js.map +1 -0
- package/dist/whoami/fetchSession.d.ts +3 -3
- package/dist/whoami/fetchSession.js +2 -4
- package/dist/whoami/fetchSession.js.map +1 -1
- package/dist/whoami/index.d.ts +1 -1
- package/package.json +20 -19
- package/dist/generated/graphql.d.ts +0 -1784
- package/dist/generated/graphql.js.map +0 -1
- package/dist/value/get.js.map +0 -1
- package/dist/value/index.js.map +0 -1
- package/dist/value/list.js.map +0 -1
- package/dist/value/set.js.map +0 -1
- /package/dist/{value → values}/index.d.ts +0 -0
- /package/dist/{value → values}/list.d.ts +0 -0
package/dist/push/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import chalk from 'chalk';
|
|
3
|
+
import parseJson from '../utils/parseJson.js';
|
|
4
4
|
import { sentenceCase } from 'change-case';
|
|
5
|
-
import {
|
|
5
|
+
import { fetchIntegrationFromFile } from '../utils/fetchLocal.js';
|
|
6
|
+
import { readGreatFile, writeGreatFile, getElementRecord, setElementRecord, removeElementRecordById, sameProvenance, } from '../utils/greatFile.js';
|
|
7
|
+
import { readElementFolder, folderToElementType, } from '../utils/elementFiles.js';
|
|
6
8
|
import createIntegrationFromFile from '../utils/createIntegrationFromFile.js';
|
|
7
|
-
import {
|
|
8
|
-
import { handleGraphQLError } from '../utils/graphqlError.js';
|
|
9
|
-
import { getApiBaseUrl } from '../utils/config.js';
|
|
9
|
+
import { getClient } from '../utils/graphqlClient.js';
|
|
10
|
+
import { getGraphQLErrorMessage, handleGraphQLError, } from '../utils/graphqlError.js';
|
|
10
11
|
import hashContent from '../utils/hashContent.js';
|
|
11
12
|
import { VALID_VALUE_TYPES, isValidValueElementType, } from '../utils/valueTypes.js';
|
|
12
|
-
import { setValueForElement } from '../
|
|
13
|
+
import { setValueForElement } from '../values/set.js';
|
|
13
14
|
import { promptForValueSetting } from '../utils/promptForValue.js';
|
|
14
|
-
import {
|
|
15
|
+
import { SHADOW_MARKER } from '../utils/writeShadowElement.js';
|
|
16
|
+
import resolveIntegrationType from '../utils/integrationType.js';
|
|
17
|
+
import { ElementType } from '../generated/schema.js';
|
|
15
18
|
import { gql } from 'graphql-tag';
|
|
16
19
|
const IntegrationForPushQueryDocument = gql `
|
|
17
20
|
query IntegrationForPush($id: ID!) {
|
|
@@ -19,6 +22,14 @@ const IntegrationForPushQueryDocument = gql `
|
|
|
19
22
|
id
|
|
20
23
|
key
|
|
21
24
|
name
|
|
25
|
+
type
|
|
26
|
+
templateImports {
|
|
27
|
+
id
|
|
28
|
+
timestamp
|
|
29
|
+
template {
|
|
30
|
+
id
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
latest {
|
|
23
34
|
timestamp
|
|
24
35
|
elements {
|
|
@@ -30,13 +41,23 @@ const IntegrationForPushQueryDocument = gql `
|
|
|
30
41
|
createdAt
|
|
31
42
|
content {
|
|
32
43
|
checksum
|
|
33
|
-
|
|
44
|
+
createdAt
|
|
45
|
+
sourceTimestamp
|
|
46
|
+
provenance
|
|
34
47
|
}
|
|
35
48
|
}
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
`;
|
|
53
|
+
const UpdateIntegrationTypeMutationDocument = gql `
|
|
54
|
+
mutation UpdateIntegrationType($id: ID!, $type: IntegrationType!) {
|
|
55
|
+
updateIntegrationType(id: $id, type: $type) {
|
|
56
|
+
id
|
|
57
|
+
type
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
40
61
|
const CreateElementMutationDocument = gql `
|
|
41
62
|
mutation CreateElement(
|
|
42
63
|
$integrationId: ID!
|
|
@@ -57,6 +78,52 @@ const CreateElementMutationDocument = gql `
|
|
|
57
78
|
}
|
|
58
79
|
}
|
|
59
80
|
`;
|
|
81
|
+
const IncludeElementMutationDocument = gql `
|
|
82
|
+
mutation IncludeElement(
|
|
83
|
+
$integrationId: ID!
|
|
84
|
+
$elementId: ID!
|
|
85
|
+
$sourceTimestamp: DateTime
|
|
86
|
+
$provenance: [ID!]
|
|
87
|
+
) {
|
|
88
|
+
includeElement(
|
|
89
|
+
integrationId: $integrationId
|
|
90
|
+
elementId: $elementId
|
|
91
|
+
sourceTimestamp: $sourceTimestamp
|
|
92
|
+
provenance: $provenance
|
|
93
|
+
) {
|
|
94
|
+
id
|
|
95
|
+
name
|
|
96
|
+
description
|
|
97
|
+
content {
|
|
98
|
+
data
|
|
99
|
+
checksum
|
|
100
|
+
createdAt
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
const RegisterTemplateImportMutationDocument = gql `
|
|
106
|
+
mutation RegisterTemplateImport(
|
|
107
|
+
$integrationId: ID!
|
|
108
|
+
$templateId: ID!
|
|
109
|
+
$timestamp: DateTime!
|
|
110
|
+
) {
|
|
111
|
+
registerTemplateImport(
|
|
112
|
+
integrationId: $integrationId
|
|
113
|
+
templateId: $templateId
|
|
114
|
+
timestamp: $timestamp
|
|
115
|
+
) {
|
|
116
|
+
id
|
|
117
|
+
templateImports {
|
|
118
|
+
id
|
|
119
|
+
timestamp
|
|
120
|
+
template {
|
|
121
|
+
id
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
60
127
|
const UpdateElementMutationDocument = gql `
|
|
61
128
|
mutation UpdateElement($id: ID!, $name: String, $description: String) {
|
|
62
129
|
updateElement(id: $id, name: $name, description: $description) {
|
|
@@ -71,7 +138,7 @@ const SetElementContentMutationDocument = gql `
|
|
|
71
138
|
setElementContent(id: $id, content: $content) {
|
|
72
139
|
id
|
|
73
140
|
content {
|
|
74
|
-
|
|
141
|
+
createdAt
|
|
75
142
|
checksum
|
|
76
143
|
}
|
|
77
144
|
}
|
|
@@ -85,38 +152,16 @@ const DeleteElementMutationDocument = gql `
|
|
|
85
152
|
}
|
|
86
153
|
}
|
|
87
154
|
`;
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
dictionaries: ElementType.Dictionary,
|
|
94
|
-
jobs: ElementType.Job,
|
|
95
|
-
values: ElementType.Value,
|
|
96
|
-
flows: ElementType.Flow,
|
|
97
|
-
retryRules: ElementType.RetryRule,
|
|
98
|
-
};
|
|
155
|
+
const SetSecretRefMutationDocument = gql `
|
|
156
|
+
mutation SetSecretRef($id: ID!, $secretRef: String!) {
|
|
157
|
+
setSecretRef(id: $id, secretRef: $secretRef)
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
99
160
|
function readLocalElements(fsImpl) {
|
|
100
161
|
const elements = [];
|
|
101
162
|
for (const folder of Object.keys(folderToElementType)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
const files = fsImpl.readdirSync(folder);
|
|
106
|
-
for (const file of files) {
|
|
107
|
-
if (!file.endsWith('.json')) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const key = file.replace('.json', '');
|
|
111
|
-
const filePath = path.join(folder, file);
|
|
112
|
-
try {
|
|
113
|
-
const content = fsImpl.readFileSync(filePath, 'utf8');
|
|
114
|
-
const data = JSON.parse(content);
|
|
115
|
-
elements.push({ folder, key, filePath, data });
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
throw new Error(`Failed to read or parse ${filePath}: ${error}`);
|
|
119
|
-
}
|
|
163
|
+
for (const { key, filePath, data } of readElementFolder(folder, undefined, fsImpl)) {
|
|
164
|
+
elements.push({ folder, key, filePath, data });
|
|
120
165
|
}
|
|
121
166
|
}
|
|
122
167
|
return elements;
|
|
@@ -125,15 +170,32 @@ function compareLocalAndRemote(local, remote) {
|
|
|
125
170
|
if (!remote) {
|
|
126
171
|
return {
|
|
127
172
|
category: 'conflict',
|
|
128
|
-
reason: 'Element exists locally with
|
|
173
|
+
reason: 'Element exists locally with an id but not found in Integreat',
|
|
129
174
|
};
|
|
130
175
|
}
|
|
131
|
-
const localChecksum = hashContent(local.data);
|
|
132
176
|
const localName = local.data.name || '';
|
|
133
177
|
const localDesc = local.data.description || '';
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
178
|
+
if (local.source) {
|
|
179
|
+
const hasNameChanged = localName !== (remote.name || '');
|
|
180
|
+
const hasDescriptionChanged = localDesc !== (remote.description || '');
|
|
181
|
+
const hasPinChanged = (local.source.timestamp ?? null) !== (remote.sourceTimestamp ?? null);
|
|
182
|
+
const hasProvenanceChanged = !sameProvenance(local.provenance, remote.provenance);
|
|
183
|
+
if (!hasNameChanged &&
|
|
184
|
+
!hasDescriptionChanged &&
|
|
185
|
+
!hasPinChanged &&
|
|
186
|
+
!hasProvenanceChanged) {
|
|
187
|
+
return { category: 'unchanged' };
|
|
188
|
+
}
|
|
189
|
+
if (hasPinChanged && local.timestamp !== remote.contentCreatedAt) {
|
|
190
|
+
return {
|
|
191
|
+
category: 'conflict',
|
|
192
|
+
reason: ' Shadow pin changed both local and remote',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { category: 'update' };
|
|
196
|
+
}
|
|
197
|
+
const localChecksum = hashContent(local.data);
|
|
198
|
+
const localTimestamp = local.timestamp ? new Date(local.timestamp) : undefined;
|
|
137
199
|
const remoteChecksum = remote.checksum || '';
|
|
138
200
|
const remoteName = remote.name || '';
|
|
139
201
|
const remoteDesc = remote.description || '';
|
|
@@ -148,7 +210,7 @@ function compareLocalAndRemote(local, remote) {
|
|
|
148
210
|
}
|
|
149
211
|
const metaDateConflict = localTimestamp && remoteCreatedAt && localTimestamp < remoteCreatedAt;
|
|
150
212
|
const conflicts = [];
|
|
151
|
-
if (hasContentChanged && local.
|
|
213
|
+
if (hasContentChanged && local.timestamp !== remote.contentCreatedAt) {
|
|
152
214
|
conflicts.push('Content changed both local and remote');
|
|
153
215
|
}
|
|
154
216
|
if (hasNameChanged && metaDateConflict) {
|
|
@@ -169,12 +231,17 @@ function compareLocalAndRemote(local, remote) {
|
|
|
169
231
|
}
|
|
170
232
|
function categorizeElements(localElements, remoteElementsMap) {
|
|
171
233
|
const toCreate = [];
|
|
234
|
+
const toInclude = [];
|
|
172
235
|
const toUpdate = [];
|
|
173
236
|
const toDelete = [];
|
|
174
237
|
const conflicts = [];
|
|
175
238
|
const localElementIds = new Set();
|
|
176
239
|
for (const local of localElements) {
|
|
177
|
-
const localId = local.
|
|
240
|
+
const localId = local.id;
|
|
241
|
+
if (local.source && !localId) {
|
|
242
|
+
toInclude.push(local);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
178
245
|
if (!localId) {
|
|
179
246
|
toCreate.push(local);
|
|
180
247
|
continue;
|
|
@@ -194,11 +261,11 @@ function categorizeElements(localElements, remoteElementsMap) {
|
|
|
194
261
|
toDelete.push(remoteLement);
|
|
195
262
|
}
|
|
196
263
|
}
|
|
197
|
-
return { toCreate, toUpdate, toDelete, conflicts };
|
|
264
|
+
return { toCreate, toInclude, toUpdate, toDelete, conflicts };
|
|
198
265
|
}
|
|
199
|
-
const prepareContentForApi = ({
|
|
266
|
+
const prepareContentForApi = ({ name, description, ...content }, elementType) => {
|
|
200
267
|
if (elementType === ElementType.Value) {
|
|
201
|
-
const { value, type, ...rest } = content;
|
|
268
|
+
const { value, ref, type, ...rest } = content;
|
|
202
269
|
if (type === undefined) {
|
|
203
270
|
throw new Error('Value element must have a "type" field. ' +
|
|
204
271
|
'Valid types: ' +
|
|
@@ -233,6 +300,65 @@ async function createElementOnApi(client, integrationId, element) {
|
|
|
233
300
|
}
|
|
234
301
|
return { id, name };
|
|
235
302
|
}
|
|
303
|
+
function writeShadowFileAndRecord(fsImpl, element, great, source, included) {
|
|
304
|
+
const name = element.data.name || included.name;
|
|
305
|
+
const description = element.data.description ||
|
|
306
|
+
included.description ||
|
|
307
|
+
undefined;
|
|
308
|
+
const content = included.content;
|
|
309
|
+
const fileData = {
|
|
310
|
+
[SHADOW_MARKER]: true,
|
|
311
|
+
...(name ? { name } : {}),
|
|
312
|
+
...(description ? { description } : {}),
|
|
313
|
+
...content?.data,
|
|
314
|
+
};
|
|
315
|
+
fsImpl.writeFileSync(element.filePath, JSON.stringify(fileData, null, 2));
|
|
316
|
+
setElementRecord(great, element.folder, element.key, {
|
|
317
|
+
id: included.id,
|
|
318
|
+
...(content?.createdAt ? { timestamp: content.createdAt } : {}),
|
|
319
|
+
...(content?.checksum ? { hash: content.checksum } : {}),
|
|
320
|
+
source,
|
|
321
|
+
...(element.provenance && element.provenance.length > 0
|
|
322
|
+
? { provenance: element.provenance }
|
|
323
|
+
: {}),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async function includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great) {
|
|
327
|
+
const source = element.source;
|
|
328
|
+
if (!source) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
const provenance = element.provenance && element.provenance.length > 0
|
|
332
|
+
? element.provenance
|
|
333
|
+
: undefined;
|
|
334
|
+
const result = await graphqlClient.request(IncludeElementMutationDocument, {
|
|
335
|
+
integrationId,
|
|
336
|
+
elementId: source.id,
|
|
337
|
+
sourceTimestamp: source.timestamp,
|
|
338
|
+
provenance,
|
|
339
|
+
});
|
|
340
|
+
if (result.error || !result.data?.includeElement) {
|
|
341
|
+
log(chalk.red(`Failed to include shadow element ${element.key} in ${element.folder}`));
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const included = result.data.includeElement;
|
|
345
|
+
const localName = element.data.name || '';
|
|
346
|
+
const localDesc = element.data.description || '';
|
|
347
|
+
const nameDiffers = localName !== (included.name || '');
|
|
348
|
+
const descDiffers = localDesc !== (included.description || '');
|
|
349
|
+
if (nameDiffers || descDiffers) {
|
|
350
|
+
const updateResult = await graphqlClient.request(UpdateElementMutationDocument, {
|
|
351
|
+
id: included.id,
|
|
352
|
+
name: nameDiffers ? localName : undefined,
|
|
353
|
+
description: descDiffers ? localDesc : undefined,
|
|
354
|
+
});
|
|
355
|
+
if (updateResult.error) {
|
|
356
|
+
log(chalk.yellow(`Warning: Failed to update metadata for shadow ${element.key} in ${element.folder}`));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
writeShadowFileAndRecord(fsImpl, element, great, source, included);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
236
362
|
async function updateElementContent(client, elementId, content) {
|
|
237
363
|
const result = await client.request(SetElementContentMutationDocument, {
|
|
238
364
|
id: elementId,
|
|
@@ -241,25 +367,36 @@ async function updateElementContent(client, elementId, content) {
|
|
|
241
367
|
if (result.error) {
|
|
242
368
|
return undefined;
|
|
243
369
|
}
|
|
244
|
-
return result.data?.setElementContent?.content?.
|
|
370
|
+
return result.data?.setElementContent?.content?.createdAt;
|
|
245
371
|
}
|
|
246
|
-
function updateLocalFile(fsImpl,
|
|
247
|
-
const content = fsImpl.readFileSync(filePath, 'utf8');
|
|
248
|
-
const parsed =
|
|
249
|
-
const {
|
|
250
|
-
const
|
|
251
|
-
const
|
|
372
|
+
function updateLocalFile(fsImpl, element, great, updatedId, updatedTimestamp, generatedName, keepValue = false) {
|
|
373
|
+
const content = fsImpl.readFileSync(element.filePath, 'utf8');
|
|
374
|
+
const parsed = parseJson(content);
|
|
375
|
+
const { name, description, value, ref, ...data } = parsed;
|
|
376
|
+
const existing = getElementRecord(great, element.folder, element.key);
|
|
377
|
+
const id = updatedId ?? existing?.id;
|
|
378
|
+
const timestamp = updatedTimestamp ?? existing?.timestamp;
|
|
252
379
|
const finalName = generatedName || name;
|
|
253
380
|
const orderedData = {
|
|
254
|
-
...(id ? { __id: id } : {}),
|
|
255
|
-
__key,
|
|
256
|
-
...(timestamp ? { __timestamp: timestamp } : {}),
|
|
257
381
|
...(finalName ? { name: finalName } : {}),
|
|
258
382
|
...(description ? { description } : {}),
|
|
259
383
|
...data,
|
|
260
384
|
...(keepValue && value !== undefined ? { value } : {}),
|
|
385
|
+
...(keepValue && ref !== undefined ? { ref } : {}),
|
|
261
386
|
};
|
|
262
|
-
fsImpl.writeFileSync(filePath, JSON.stringify(orderedData, null, 2));
|
|
387
|
+
fsImpl.writeFileSync(element.filePath, JSON.stringify(orderedData, null, 2));
|
|
388
|
+
setElementRecord(great, element.folder, element.key, {
|
|
389
|
+
...(id ? { id } : {}),
|
|
390
|
+
...(timestamp ? { timestamp } : {}),
|
|
391
|
+
hash: hashContent(orderedData),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function recordElementProgress(great, element, fields) {
|
|
395
|
+
const existing = getElementRecord(great, element.folder, element.key);
|
|
396
|
+
setElementRecord(great, element.folder, element.key, {
|
|
397
|
+
...existing,
|
|
398
|
+
...fields,
|
|
399
|
+
});
|
|
263
400
|
}
|
|
264
401
|
function buildRemoteElementsMap(elements) {
|
|
265
402
|
const remoteElements = new Map();
|
|
@@ -267,10 +404,12 @@ function buildRemoteElementsMap(elements) {
|
|
|
267
404
|
remoteElements.set(element.id, {
|
|
268
405
|
id: element.id,
|
|
269
406
|
checksum: element.content?.checksum,
|
|
270
|
-
|
|
407
|
+
contentCreatedAt: element.content?.createdAt,
|
|
271
408
|
name: element.name,
|
|
272
409
|
description: element.description,
|
|
273
410
|
createdAt: element.createdAt,
|
|
411
|
+
sourceTimestamp: element.content?.sourceTimestamp,
|
|
412
|
+
provenance: element.content?.provenance,
|
|
274
413
|
});
|
|
275
414
|
}
|
|
276
415
|
return remoteElements;
|
|
@@ -283,7 +422,7 @@ function handleConflicts(conflicts, log) {
|
|
|
283
422
|
}
|
|
284
423
|
log(`\nRun ${chalk.bold('great pull')} to get remote changes, then manually merge and push again.`);
|
|
285
424
|
}
|
|
286
|
-
async function createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue) {
|
|
425
|
+
async function createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue, great) {
|
|
287
426
|
let id = element.remoteId;
|
|
288
427
|
let generatedName;
|
|
289
428
|
if (!id) {
|
|
@@ -294,6 +433,7 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
|
|
|
294
433
|
}
|
|
295
434
|
id = result.id;
|
|
296
435
|
generatedName = result.name;
|
|
436
|
+
recordElementProgress(great, element, { id });
|
|
297
437
|
}
|
|
298
438
|
else {
|
|
299
439
|
const apiElement = apiElementsMap.get(id);
|
|
@@ -316,6 +456,15 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
|
|
|
316
456
|
}
|
|
317
457
|
}
|
|
318
458
|
}
|
|
459
|
+
if (element.source) {
|
|
460
|
+
const remote = apiElementsMap.get(id);
|
|
461
|
+
const pinChanged = (element.source.timestamp ?? null) !== (remote?.sourceTimestamp ?? null);
|
|
462
|
+
const provenanceChanged = !sameProvenance(element.provenance, remote?.provenance);
|
|
463
|
+
if (pinChanged || provenanceChanged) {
|
|
464
|
+
return includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great);
|
|
465
|
+
}
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
319
468
|
const elementType = folderToElementType[element.folder];
|
|
320
469
|
let content;
|
|
321
470
|
try {
|
|
@@ -330,17 +479,31 @@ async function createOrUpdateElement(element, graphqlClient, integrationId, fsIm
|
|
|
330
479
|
log(chalk.red(`Failed to ${element.remoteId ? 'update' : 'set'} content for ${element.key} in ${element.folder}`));
|
|
331
480
|
return false;
|
|
332
481
|
}
|
|
482
|
+
recordElementProgress(great, element, { id, timestamp });
|
|
333
483
|
const hasValue = element.data.value !== undefined;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (!
|
|
338
|
-
|
|
339
|
-
|
|
484
|
+
const hasRef = element.data.ref !== undefined;
|
|
485
|
+
const isSecret = content.type === 'secret';
|
|
486
|
+
if (elementType === ElementType.Value && shouldSetValue) {
|
|
487
|
+
if (isSecret && hasRef && !hasValue) {
|
|
488
|
+
const secretRef = String(element.data.ref);
|
|
489
|
+
const result = await graphqlClient.request(SetSecretRefMutationDocument, { id, secretRef });
|
|
490
|
+
if (result.error) {
|
|
491
|
+
log(chalk.red(`Failed to set secret ref for ${element.key}: ${getGraphQLErrorMessage(result.error)}`));
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
log(chalk.green(`✓ Secret ref set for element "${element.key}"`));
|
|
495
|
+
}
|
|
496
|
+
else if (hasValue) {
|
|
497
|
+
const valueStr = String(element.data.value);
|
|
498
|
+
const result = await setValueForElement(id, valueStr, content, graphqlClient);
|
|
499
|
+
if (!result.success) {
|
|
500
|
+
log(chalk.red(`Failed to set value for ${element.key}: ${result.error}`));
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
log(chalk.green(`✓ Value set for element "${element.key}" (${content.type})`));
|
|
340
504
|
}
|
|
341
|
-
log(chalk.green(`✓ Value set for element "${element.key}" (${content.type})`));
|
|
342
505
|
}
|
|
343
|
-
updateLocalFile(fsImpl, element
|
|
506
|
+
updateLocalFile(fsImpl, element, great, element.remoteId ? undefined : id, timestamp, generatedName, !shouldSetValue && hasValue);
|
|
344
507
|
return true;
|
|
345
508
|
}
|
|
346
509
|
async function deleteElement(element, graphqlClient, log) {
|
|
@@ -353,29 +516,96 @@ async function deleteElement(element, graphqlClient, log) {
|
|
|
353
516
|
}
|
|
354
517
|
return true;
|
|
355
518
|
}
|
|
356
|
-
function
|
|
357
|
-
const
|
|
519
|
+
function buildRemoteImportsMap(templateImports) {
|
|
520
|
+
const map = new Map();
|
|
521
|
+
for (const templateImport of templateImports) {
|
|
522
|
+
if (templateImport.template) {
|
|
523
|
+
map.set(templateImport.template.id, templateImport);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return map;
|
|
527
|
+
}
|
|
528
|
+
function findImportsToRegister(great, remoteImports) {
|
|
529
|
+
return (great.imports ?? []).filter((record) => {
|
|
530
|
+
const remote = remoteImports.get(record.templateId);
|
|
531
|
+
return !remote || remote.timestamp !== record.timestamp;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function rebuildImportRecords(great, templateImports) {
|
|
535
|
+
const records = [];
|
|
536
|
+
for (const templateImport of templateImports) {
|
|
537
|
+
if (templateImport.template) {
|
|
538
|
+
records.push({
|
|
539
|
+
templateId: templateImport.template.id,
|
|
540
|
+
timestamp: templateImport.timestamp,
|
|
541
|
+
id: templateImport.id,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (records.length > 0 || great.imports?.length) {
|
|
546
|
+
great.imports = records;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async function registerTemplateImports(pending, initialRemoteImports, graphqlClient, integrationId, great, log) {
|
|
550
|
+
let latestRemoteImports = initialRemoteImports;
|
|
551
|
+
let registeredCount = 0;
|
|
552
|
+
for (const record of pending) {
|
|
553
|
+
const result = await graphqlClient.request(RegisterTemplateImportMutationDocument, {
|
|
554
|
+
integrationId,
|
|
555
|
+
templateId: record.templateId,
|
|
556
|
+
timestamp: record.timestamp,
|
|
557
|
+
});
|
|
558
|
+
if (result.error || !result.data?.registerTemplateImport) {
|
|
559
|
+
log(chalk.red(`Failed to register template import ${record.templateId}`));
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
latestRemoteImports = result.data.registerTemplateImport.templateImports;
|
|
563
|
+
log(chalk.green(`✓ Registered template import ${record.templateId}`));
|
|
564
|
+
registeredCount++;
|
|
565
|
+
}
|
|
566
|
+
rebuildImportRecords(great, latestRemoteImports);
|
|
567
|
+
return registeredCount;
|
|
568
|
+
}
|
|
569
|
+
async function syncIntegrationType(graphqlClient, integrationId, remoteType, localType, log) {
|
|
570
|
+
if (localType === remoteType) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const result = await graphqlClient.request(UpdateIntegrationTypeMutationDocument, { id: integrationId, type: localType });
|
|
574
|
+
if (result.error) {
|
|
575
|
+
handleGraphQLError(result.error, log);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
log(`${chalk.green('✓')} Updated integration type to ${chalk.cyan(localType)}`);
|
|
579
|
+
}
|
|
580
|
+
function logSummary(createdCount, includedCount, updatedCount, deletedCount, registeredCount, log) {
|
|
581
|
+
const total = createdCount + includedCount + updatedCount + deletedCount + registeredCount;
|
|
358
582
|
if (total > 0) {
|
|
359
583
|
const parts = [];
|
|
360
584
|
if (createdCount > 0)
|
|
361
585
|
parts.push(`${createdCount} created`);
|
|
586
|
+
if (includedCount > 0)
|
|
587
|
+
parts.push(`${includedCount} included`);
|
|
362
588
|
if (updatedCount > 0)
|
|
363
589
|
parts.push(`${updatedCount} updated`);
|
|
364
590
|
if (deletedCount > 0)
|
|
365
591
|
parts.push(`${deletedCount} deleted`);
|
|
592
|
+
if (registeredCount > 0)
|
|
593
|
+
parts.push(`${registeredCount} template imports registered`);
|
|
366
594
|
log(`\n${chalk.green('✓')} ${parts.join(', ')}`);
|
|
367
595
|
}
|
|
368
596
|
}
|
|
369
597
|
export const action = (log, apiToken, client, fsImpl = fs, config, promptForValueSettingFn = promptForValueSetting) => async function pushIntegration(options) {
|
|
370
|
-
|
|
598
|
+
const great = readGreatFile(undefined, fsImpl);
|
|
599
|
+
let integrationId = great.id;
|
|
371
600
|
if (!integrationId) {
|
|
372
|
-
log(chalk.yellow('No integration ID found
|
|
601
|
+
log(chalk.yellow('No integration ID found'));
|
|
373
602
|
log('Creating integration on server...\n');
|
|
374
|
-
const
|
|
375
|
-
const graphqlClient = client ?? createGraphQLClient(apiToken, apiBaseUrl);
|
|
603
|
+
const graphqlClient = getClient(apiToken, client, config);
|
|
376
604
|
const result = await createIntegrationFromFile(graphqlClient, apiToken ?? '', options ?? {}, config, fsImpl);
|
|
377
605
|
if (!result.success || !result.integration) {
|
|
378
|
-
|
|
606
|
+
if (result.error) {
|
|
607
|
+
handleGraphQLError(result.error, log);
|
|
608
|
+
}
|
|
379
609
|
return;
|
|
380
610
|
}
|
|
381
611
|
const { integration } = result;
|
|
@@ -390,27 +620,26 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
|
|
|
390
620
|
return;
|
|
391
621
|
}
|
|
392
622
|
const updatedData = {
|
|
393
|
-
__id: integration.id,
|
|
394
|
-
__key: integration.key,
|
|
395
623
|
name: integration.name,
|
|
396
624
|
description: fileData.description,
|
|
397
625
|
status: integration.status,
|
|
398
|
-
|
|
399
|
-
__id: integration.account.id,
|
|
400
|
-
__key: integration.account.key,
|
|
401
|
-
name: integration.account.name,
|
|
402
|
-
},
|
|
626
|
+
type: integration.type,
|
|
403
627
|
flags: fileData.flags,
|
|
404
628
|
identConfig: fileData.identConfig,
|
|
405
629
|
nonvalues: fileData.nonvalues,
|
|
406
630
|
queueService: fileData.queueService,
|
|
407
631
|
};
|
|
408
632
|
fsImpl.writeFileSync('integration.json', JSON.stringify(updatedData, null, 2));
|
|
633
|
+
great.id = integration.id;
|
|
634
|
+
great.account = {
|
|
635
|
+
id: integration.account.id,
|
|
636
|
+
key: integration.account.key,
|
|
637
|
+
};
|
|
638
|
+
writeGreatFile(great, undefined, fsImpl);
|
|
409
639
|
integrationId = integration.id;
|
|
410
640
|
log(chalk.green('✓ Updated integration.json with server details\n'));
|
|
411
641
|
}
|
|
412
|
-
const
|
|
413
|
-
const graphqlClient = client ?? createGraphQLClient(apiToken, apiBaseUrl);
|
|
642
|
+
const graphqlClient = getClient(apiToken, client, config);
|
|
414
643
|
const result = await graphqlClient.request(IntegrationForPushQueryDocument, { id: integrationId });
|
|
415
644
|
if (result.error) {
|
|
416
645
|
handleGraphQLError(result.error, log);
|
|
@@ -421,6 +650,8 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
|
|
|
421
650
|
log(chalk.red('Integration has no latest version'));
|
|
422
651
|
return;
|
|
423
652
|
}
|
|
653
|
+
const localData = fetchIntegrationFromFile(fsImpl);
|
|
654
|
+
await syncIntegrationType(graphqlClient, integrationId, resolveIntegrationType(integration.type), resolveIntegrationType(localData?.type, log), log);
|
|
424
655
|
const apiElementsMap = buildRemoteElementsMap(integration.latest.elements);
|
|
425
656
|
let localElements;
|
|
426
657
|
try {
|
|
@@ -430,12 +661,26 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
|
|
|
430
661
|
log(chalk.red(`Error reading local files: ${error}`));
|
|
431
662
|
return;
|
|
432
663
|
}
|
|
433
|
-
const
|
|
664
|
+
for (const local of localElements) {
|
|
665
|
+
const record = getElementRecord(great, local.folder, local.key);
|
|
666
|
+
local.id = record?.id;
|
|
667
|
+
local.timestamp = record?.timestamp;
|
|
668
|
+
local.source = record?.source;
|
|
669
|
+
local.provenance = record?.provenance;
|
|
670
|
+
}
|
|
671
|
+
const { toCreate, toInclude, toUpdate, toDelete, conflicts } = categorizeElements(localElements, apiElementsMap);
|
|
434
672
|
if (conflicts.length > 0) {
|
|
435
673
|
handleConflicts(conflicts, log);
|
|
436
674
|
return;
|
|
437
675
|
}
|
|
438
|
-
|
|
676
|
+
const remoteImportsMap = buildRemoteImportsMap(integration.templateImports ?? []);
|
|
677
|
+
const importsToRegister = findImportsToRegister(great, remoteImportsMap);
|
|
678
|
+
if (toCreate.length +
|
|
679
|
+
toInclude.length +
|
|
680
|
+
toUpdate.length +
|
|
681
|
+
toDelete.length +
|
|
682
|
+
importsToRegister.length ===
|
|
683
|
+
0) {
|
|
439
684
|
log(chalk.green('No changes to push'));
|
|
440
685
|
return;
|
|
441
686
|
}
|
|
@@ -447,31 +692,57 @@ export const action = (log, apiToken, client, fsImpl = fs, config, promptForValu
|
|
|
447
692
|
log(chalk.yellow('\nPush cancelled'));
|
|
448
693
|
return;
|
|
449
694
|
}
|
|
695
|
+
const persistProgress = () => writeGreatFile(great, undefined, fsImpl);
|
|
450
696
|
let createdCount = 0;
|
|
451
697
|
for (const element of toCreate) {
|
|
452
|
-
const success = await createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue);
|
|
453
|
-
if (!success)
|
|
698
|
+
const success = await createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue, great);
|
|
699
|
+
if (!success) {
|
|
700
|
+
persistProgress();
|
|
454
701
|
return;
|
|
702
|
+
}
|
|
455
703
|
log(chalk.green(`✓ Created ${element.folder}/${element.key}`));
|
|
456
704
|
createdCount++;
|
|
457
705
|
}
|
|
706
|
+
let includedCount = 0;
|
|
707
|
+
for (const element of toInclude) {
|
|
708
|
+
const success = await includeShadowElement(element, graphqlClient, integrationId, fsImpl, log, great);
|
|
709
|
+
if (!success) {
|
|
710
|
+
persistProgress();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
log(chalk.green(`✓ Included ${element.folder}/${element.key}`));
|
|
714
|
+
includedCount++;
|
|
715
|
+
}
|
|
458
716
|
let updatedCount = 0;
|
|
459
717
|
for (const element of toUpdate) {
|
|
460
|
-
const success = await createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue);
|
|
461
|
-
if (!success)
|
|
718
|
+
const success = await createOrUpdateElement(element, graphqlClient, integrationId, fsImpl, log, apiElementsMap, shouldSetValue, great);
|
|
719
|
+
if (!success) {
|
|
720
|
+
persistProgress();
|
|
462
721
|
return;
|
|
722
|
+
}
|
|
463
723
|
log(chalk.green(`✓ Updated ${element.folder}/${element.key}`));
|
|
464
724
|
updatedCount++;
|
|
465
725
|
}
|
|
466
726
|
let deletedCount = 0;
|
|
467
727
|
for (const element of toDelete) {
|
|
468
728
|
const success = await deleteElement(element, graphqlClient, log);
|
|
469
|
-
if (!success)
|
|
729
|
+
if (!success) {
|
|
730
|
+
persistProgress();
|
|
470
731
|
return;
|
|
732
|
+
}
|
|
733
|
+
removeElementRecordById(great, element.id);
|
|
471
734
|
log(chalk.green(`✓ Deleted ${element.id}`));
|
|
472
735
|
deletedCount++;
|
|
473
736
|
}
|
|
474
|
-
|
|
737
|
+
const registeredCount = await registerTemplateImports(importsToRegister, integration.templateImports ?? [], graphqlClient, integrationId, great, log);
|
|
738
|
+
if (registeredCount === undefined) {
|
|
739
|
+
persistProgress();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const localKeys = new Set(localElements.map((el) => `${el.folder}/${el.key}`));
|
|
743
|
+
great.elements = Object.fromEntries(Object.entries(great.elements).filter(([key]) => localKeys.has(key)));
|
|
744
|
+
writeGreatFile(great, undefined, fsImpl);
|
|
745
|
+
logSummary(createdCount, includedCount, updatedCount, deletedCount, registeredCount, log);
|
|
475
746
|
};
|
|
476
747
|
export default (program, apiToken) => program
|
|
477
748
|
.command('push')
|