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