@aws-amplify/amplify-util-uibuilder 1.9.4 → 1.10.0-rc.0ffe9f92c0.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/CHANGELOG.md +11 -0
- package/lib/clients/amplify-studio-client.d.ts +1 -0
- package/lib/clients/amplify-studio-client.d.ts.map +1 -1
- package/lib/clients/amplify-studio-client.js +3 -1
- package/lib/clients/amplify-studio-client.js.map +1 -1
- package/lib/commands/generateComponents.d.ts.map +1 -1
- package/lib/commands/generateComponents.js +12 -4
- package/lib/commands/generateComponents.js.map +1 -1
- package/lib/commands/utils/getApiConfiguration.d.ts +8 -0
- package/lib/commands/utils/getApiConfiguration.d.ts.map +1 -0
- package/lib/commands/utils/getApiConfiguration.js +70 -0
- package/lib/commands/utils/getApiConfiguration.js.map +1 -0
- package/package.json +7 -7
- package/src/__tests__/.eslintrc.js +9 -0
- package/src/__tests__/generateComponents.test.ts +165 -92
- package/src/__tests__/utils/getApiConfiguration.test.ts +41 -0
- package/src/clients/amplify-studio-client.ts +3 -0
- package/src/commands/generateComponents.ts +13 -6
- package/src/commands/utils/getApiConfiguration.ts +73 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/amplify-codegen.d.ts +12 -0
|
@@ -3,6 +3,8 @@ import * as utils from '../commands/utils';
|
|
|
3
3
|
import { run } from '../commands/generateComponents';
|
|
4
4
|
import { isDataStoreEnabled } from '@aws-amplify/amplify-category-api';
|
|
5
5
|
import { getTransformerVersion } from '../commands/utils/featureFlags';
|
|
6
|
+
import { getCodegenConfig } from 'amplify-codegen';
|
|
7
|
+
import { getUiBuilderComponentsPath } from '../commands/utils/getUiBuilderComponentsPath';
|
|
6
8
|
|
|
7
9
|
jest.mock('../commands/utils');
|
|
8
10
|
jest.mock('@aws-amplify/amplify-cli-core');
|
|
@@ -14,11 +16,22 @@ jest.mock('../commands/utils/featureFlags', () => ({
|
|
|
14
16
|
...jest.requireActual('../commands/utils/featureFlags'),
|
|
15
17
|
getTransformerVersion: jest.fn(),
|
|
16
18
|
}));
|
|
19
|
+
jest.mock('../commands/utils/getUiBuilderComponentsPath', () => ({
|
|
20
|
+
...jest.requireActual('../commands/utils/getUiBuilderComponentsPath'),
|
|
21
|
+
getUiBuilderComponentsPath: jest.fn(),
|
|
22
|
+
}));
|
|
23
|
+
jest.mock('amplify-codegen', () => ({
|
|
24
|
+
...jest.requireActual('amplify-codegen'),
|
|
25
|
+
getCodegenConfig: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
17
28
|
const awsMock = aws as any;
|
|
18
29
|
const utilsMock = utils as any;
|
|
30
|
+
const isDataStoreEnabledMocked = isDataStoreEnabled as any;
|
|
31
|
+
const getTransformerVersionMocked = getTransformerVersion as any;
|
|
32
|
+
const getCodegenConfigMocked = getCodegenConfig as any;
|
|
33
|
+
const getUiBuilderComponentsPathMocked = getUiBuilderComponentsPath as any;
|
|
19
34
|
|
|
20
|
-
const isDataStoreEnabledMocked = jest.mocked(isDataStoreEnabled);
|
|
21
|
-
const getTransformerVersionMocked = jest.mocked(getTransformerVersion);
|
|
22
35
|
utilsMock.shouldRenderComponents = jest.fn().mockReturnValue(true);
|
|
23
36
|
utilsMock.notifyMissingPackages = jest.fn().mockReturnValue(true);
|
|
24
37
|
utilsMock.getAmplifyDataSchema = jest.fn().mockReturnValue({});
|
|
@@ -32,18 +45,23 @@ jest.mock('../commands/utils/featureFlags', () => ({
|
|
|
32
45
|
getTransformerVersion: jest.fn().mockReturnValue(2),
|
|
33
46
|
}));
|
|
34
47
|
|
|
48
|
+
const defaultStudioFeatureFlags = {
|
|
49
|
+
autoGenerateForms: 'true',
|
|
50
|
+
autoGenerateViews: 'true',
|
|
51
|
+
isRelationshipSupported: 'false',
|
|
52
|
+
isNonModelSupported: 'false',
|
|
53
|
+
isGraphQLEnabled: 'true',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const projectPath = '/usr/test/test-project';
|
|
57
|
+
|
|
35
58
|
describe('can generate components', () => {
|
|
36
59
|
let context: any;
|
|
37
60
|
let schemas: any;
|
|
38
61
|
let mockedExport: jest.Mock<any, any>;
|
|
39
62
|
const getMetadataPromise = jest.fn().mockReturnValue({
|
|
40
63
|
features: {
|
|
41
|
-
|
|
42
|
-
autoGenerateViews: 'true',
|
|
43
|
-
formFeatureFlags: {
|
|
44
|
-
isRelationshipSupported: 'false',
|
|
45
|
-
isNonModelSupported: 'false',
|
|
46
|
-
},
|
|
64
|
+
...defaultStudioFeatureFlags,
|
|
47
65
|
},
|
|
48
66
|
});
|
|
49
67
|
const startCodegenJobPromise = jest.fn().mockReturnValue({
|
|
@@ -53,6 +71,7 @@ describe('can generate components', () => {
|
|
|
53
71
|
promise: startCodegenJobPromise,
|
|
54
72
|
});
|
|
55
73
|
beforeEach(() => {
|
|
74
|
+
jest.clearAllMocks();
|
|
56
75
|
isDataStoreEnabledMocked.mockResolvedValue(true);
|
|
57
76
|
getTransformerVersionMocked.mockResolvedValue(2);
|
|
58
77
|
context = {
|
|
@@ -65,6 +84,11 @@ describe('can generate components', () => {
|
|
|
65
84
|
envName: 'testEnvName',
|
|
66
85
|
},
|
|
67
86
|
},
|
|
87
|
+
exeInfo: {
|
|
88
|
+
localEnvInfo: {
|
|
89
|
+
projectPath,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
68
92
|
};
|
|
69
93
|
schemas = {
|
|
70
94
|
entities: [
|
|
@@ -81,6 +105,16 @@ describe('can generate components', () => {
|
|
|
81
105
|
},
|
|
82
106
|
],
|
|
83
107
|
};
|
|
108
|
+
|
|
109
|
+
getCodegenConfigMocked.mockReturnValue({
|
|
110
|
+
getGeneratedTypesPath: jest.fn().mockReturnValue(undefined),
|
|
111
|
+
getGeneratedQueriesPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/queries.js'),
|
|
112
|
+
getGeneratedMutationsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/mutations.js'),
|
|
113
|
+
getGeneratedSubscriptionsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/subscriptions.js'),
|
|
114
|
+
getGeneratedFragmentsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/fragments.js'),
|
|
115
|
+
getQueryMaxDepth: jest.fn().mockReturnValue(3),
|
|
116
|
+
});
|
|
117
|
+
|
|
84
118
|
mockedExport = jest.fn().mockReturnValue({
|
|
85
119
|
entities: schemas.entities,
|
|
86
120
|
});
|
|
@@ -105,10 +139,10 @@ describe('can generate components', () => {
|
|
|
105
139
|
promise: jest.fn().mockReturnValue({ status: 'succeeded' }),
|
|
106
140
|
}),
|
|
107
141
|
});
|
|
142
|
+
getUiBuilderComponentsPathMocked.mockReturnValue(projectPath + '/src/ui-components');
|
|
108
143
|
utilsMock.generateUiBuilderComponents = jest.fn().mockReturnValue(schemas.entities);
|
|
109
144
|
utilsMock.generateUiBuilderThemes = jest.fn().mockReturnValue(schemas.entities);
|
|
110
145
|
utilsMock.generateUiBuilderForms = jest.fn().mockReturnValue(schemas.entities);
|
|
111
|
-
utilsMock.getAmplifyDataSchema = jest.fn().mockReturnValue(undefined);
|
|
112
146
|
utilsMock.generateAmplifyUiBuilderIndexFile = jest.fn().mockReturnValue(true);
|
|
113
147
|
utilsMock.generateAmplifyUiBuilderUtilFile = jest.fn().mockReturnValue(true);
|
|
114
148
|
utilsMock.deleteDetachedForms = jest.fn();
|
|
@@ -120,7 +154,7 @@ describe('can generate components', () => {
|
|
|
120
154
|
expect(mockedExport).toBeCalledTimes(3);
|
|
121
155
|
expect(startCodegenJobPromise).toBeCalledTimes(1);
|
|
122
156
|
expect(utilsMock.waitForSucceededJob).toBeCalledTimes(1);
|
|
123
|
-
expect(
|
|
157
|
+
expect(getUiBuilderComponentsPathMocked).toBeCalledTimes(1);
|
|
124
158
|
expect(utilsMock.extractUIComponents).toBeCalledTimes(1);
|
|
125
159
|
expect(utilsMock.deleteDetachedForms).toBeCalledTimes(1);
|
|
126
160
|
});
|
|
@@ -130,34 +164,14 @@ describe('can generate components', () => {
|
|
|
130
164
|
getTransformerVersionMocked.mockResolvedValue(2);
|
|
131
165
|
getMetadataPromise.mockReturnValue({
|
|
132
166
|
features: {
|
|
133
|
-
|
|
134
|
-
autoGenerateViews: 'true',
|
|
135
|
-
formFeatureFlags: {
|
|
136
|
-
isRelationshipSupported: 'false',
|
|
137
|
-
isNonModelSupported: 'false',
|
|
138
|
-
},
|
|
167
|
+
...defaultStudioFeatureFlags,
|
|
139
168
|
},
|
|
140
169
|
});
|
|
141
170
|
await run(context, 'PostPull');
|
|
142
171
|
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
143
172
|
appId: 'testAppId',
|
|
144
173
|
environmentName: 'testEnvName',
|
|
145
|
-
codegenJobToCreate: {
|
|
146
|
-
renderConfig: {
|
|
147
|
-
react: {
|
|
148
|
-
module: 'es2020',
|
|
149
|
-
target: 'es2020',
|
|
150
|
-
script: 'jsx',
|
|
151
|
-
renderTypeDeclarations: true,
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
genericDataSchema: undefined,
|
|
155
|
-
autoGenerateForms: true,
|
|
156
|
-
features: {
|
|
157
|
-
isNonModelSupported: false,
|
|
158
|
-
isRelationshipSupported: false,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
174
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: true }),
|
|
161
175
|
});
|
|
162
176
|
});
|
|
163
177
|
|
|
@@ -166,104 +180,163 @@ describe('can generate components', () => {
|
|
|
166
180
|
getTransformerVersionMocked.mockResolvedValue(1);
|
|
167
181
|
getMetadataPromise.mockReturnValue({
|
|
168
182
|
features: {
|
|
169
|
-
|
|
170
|
-
autoGenerateViews: 'true',
|
|
171
|
-
formFeatureFlags: {
|
|
172
|
-
isRelationshipSupported: 'false',
|
|
173
|
-
isNonModelSupported: 'false',
|
|
174
|
-
},
|
|
183
|
+
...defaultStudioFeatureFlags,
|
|
175
184
|
},
|
|
176
185
|
});
|
|
177
186
|
await run(context, 'PostPull');
|
|
178
187
|
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
179
188
|
appId: 'testAppId',
|
|
180
189
|
environmentName: 'testEnvName',
|
|
181
|
-
codegenJobToCreate: {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
autoGenerateForms: false,
|
|
192
|
-
features: {
|
|
193
|
-
isNonModelSupported: false,
|
|
194
|
-
isRelationshipSupported: false,
|
|
195
|
-
},
|
|
190
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }),
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should not autogenerate forms if datastore is not enabled and GraphQL is not enabled', async () => {
|
|
195
|
+
isDataStoreEnabledMocked.mockResolvedValue(false);
|
|
196
|
+
getMetadataPromise.mockReturnValue({
|
|
197
|
+
features: {
|
|
198
|
+
...defaultStudioFeatureFlags,
|
|
199
|
+
isGraphQLEnabled: 'false',
|
|
196
200
|
},
|
|
197
201
|
});
|
|
202
|
+
await run(context, 'PostPull');
|
|
203
|
+
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
204
|
+
appId: 'testAppId',
|
|
205
|
+
environmentName: 'testEnvName',
|
|
206
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }),
|
|
207
|
+
});
|
|
198
208
|
});
|
|
199
209
|
|
|
200
|
-
it('should not autogenerate forms if datastore is not enabled', async () => {
|
|
210
|
+
it('should not autogenerate forms if datastore is not enabled and GraphQL is enabled with invalid config', async () => {
|
|
201
211
|
isDataStoreEnabledMocked.mockResolvedValue(false);
|
|
202
212
|
getMetadataPromise.mockReturnValue({
|
|
203
213
|
features: {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
formFeatureFlags: {
|
|
207
|
-
isRelationshipSupported: 'false',
|
|
208
|
-
isNonModelSupported: 'false',
|
|
209
|
-
},
|
|
214
|
+
...defaultStudioFeatureFlags,
|
|
215
|
+
isGraphQLEnabled: 'true',
|
|
210
216
|
},
|
|
211
217
|
});
|
|
218
|
+
getCodegenConfigMocked.mockImplementation(() => {
|
|
219
|
+
throw new Error();
|
|
220
|
+
});
|
|
212
221
|
await run(context, 'PostPull');
|
|
213
222
|
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
214
223
|
appId: 'testAppId',
|
|
215
224
|
environmentName: 'testEnvName',
|
|
216
|
-
codegenJobToCreate: {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
autoGenerateForms: false,
|
|
227
|
-
features: {
|
|
228
|
-
isNonModelSupported: false,
|
|
229
|
-
isRelationshipSupported: false,
|
|
230
|
-
},
|
|
225
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }),
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should autogenerate forms if datastore is not enabled and GraphQL is enabled with valid config', async () => {
|
|
230
|
+
isDataStoreEnabledMocked.mockResolvedValue(false);
|
|
231
|
+
getMetadataPromise.mockReturnValue({
|
|
232
|
+
features: {
|
|
233
|
+
...defaultStudioFeatureFlags,
|
|
234
|
+
isGraphQLEnabled: 'true',
|
|
231
235
|
},
|
|
232
236
|
});
|
|
237
|
+
await run(context, 'PostPull');
|
|
238
|
+
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
239
|
+
appId: 'testAppId',
|
|
240
|
+
environmentName: 'testEnvName',
|
|
241
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: true }),
|
|
242
|
+
});
|
|
233
243
|
});
|
|
234
244
|
|
|
235
245
|
it('should not autogenerate forms if feature flag is not enabled', async () => {
|
|
236
246
|
isDataStoreEnabledMocked.mockResolvedValue(true);
|
|
237
247
|
getMetadataPromise.mockReturnValue({
|
|
238
248
|
features: {
|
|
249
|
+
...defaultStudioFeatureFlags,
|
|
239
250
|
autoGenerateForms: 'false',
|
|
240
|
-
autoGenerateViews: 'true',
|
|
241
|
-
formFeatureFlags: {
|
|
242
|
-
isRelationshipSupported: 'false',
|
|
243
|
-
isNonModelSupported: 'false',
|
|
244
|
-
},
|
|
245
251
|
},
|
|
246
252
|
});
|
|
247
253
|
await run(context, 'PostPull');
|
|
248
254
|
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
249
255
|
appId: 'testAppId',
|
|
250
256
|
environmentName: 'testEnvName',
|
|
251
|
-
codegenJobToCreate: {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }),
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('codegen job creation', () => {
|
|
262
|
+
it('should inclue dataStore configuration when dataStore is enabled', async () => {
|
|
263
|
+
isDataStoreEnabledMocked.mockResolvedValue(true);
|
|
264
|
+
getMetadataPromise.mockReturnValue({
|
|
265
|
+
features: {
|
|
266
|
+
...defaultStudioFeatureFlags,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
await run(context, 'PostPull');
|
|
270
|
+
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
271
|
+
appId: 'testAppId',
|
|
272
|
+
environmentName: 'testEnvName',
|
|
273
|
+
codegenJobToCreate: expect.objectContaining({
|
|
274
|
+
renderConfig: {
|
|
275
|
+
react: expect.objectContaining({
|
|
276
|
+
apiConfiguration: {
|
|
277
|
+
dataStoreConfig: {},
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
258
280
|
},
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should inclue GraphQL configuration when dataStore is disabled and valid api configuration is found', async () => {
|
|
286
|
+
isDataStoreEnabledMocked.mockResolvedValue(false);
|
|
287
|
+
getMetadataPromise.mockReturnValue({
|
|
288
|
+
features: {
|
|
289
|
+
...defaultStudioFeatureFlags,
|
|
290
|
+
isGraphQLEnabled: 'true',
|
|
259
291
|
},
|
|
260
|
-
|
|
261
|
-
|
|
292
|
+
});
|
|
293
|
+
await run(context, 'PostPull');
|
|
294
|
+
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
295
|
+
appId: 'testAppId',
|
|
296
|
+
environmentName: 'testEnvName',
|
|
297
|
+
codegenJobToCreate: expect.objectContaining({
|
|
298
|
+
renderConfig: {
|
|
299
|
+
react: expect.objectContaining({
|
|
300
|
+
apiConfiguration: {
|
|
301
|
+
graphQLConfig: {
|
|
302
|
+
fragmentsFilePath: '../graphql/fragments.js',
|
|
303
|
+
mutationsFilePath: '../graphql/mutations.js',
|
|
304
|
+
queriesFilePath: '../graphql/queries.js',
|
|
305
|
+
subscriptionsFilePath: '../graphql/subscriptions.js',
|
|
306
|
+
typesFilePath: '',
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should inclue noApi configuration when dataStore is disabled and no valid GraphQL Api', async () => {
|
|
316
|
+
isDataStoreEnabledMocked.mockResolvedValue(false);
|
|
317
|
+
getMetadataPromise.mockReturnValue({
|
|
262
318
|
features: {
|
|
263
|
-
|
|
264
|
-
|
|
319
|
+
...defaultStudioFeatureFlags,
|
|
320
|
+
isGraphQLEnabled: 'true',
|
|
265
321
|
},
|
|
266
|
-
}
|
|
322
|
+
});
|
|
323
|
+
getCodegenConfigMocked.mockImplementation(() => {
|
|
324
|
+
throw new Error();
|
|
325
|
+
});
|
|
326
|
+
await run(context, 'PostPull');
|
|
327
|
+
expect(mockStartCodegenJob).toHaveBeenCalledWith({
|
|
328
|
+
appId: 'testAppId',
|
|
329
|
+
environmentName: 'testEnvName',
|
|
330
|
+
codegenJobToCreate: expect.objectContaining({
|
|
331
|
+
renderConfig: {
|
|
332
|
+
react: expect.objectContaining({
|
|
333
|
+
apiConfiguration: {
|
|
334
|
+
noApiConfig: {},
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
},
|
|
338
|
+
}),
|
|
339
|
+
});
|
|
267
340
|
});
|
|
268
341
|
});
|
|
269
342
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
2
|
+
import { relativeToComponentsPath } from '../../commands/utils/getApiConfiguration';
|
|
3
|
+
import { getUiBuilderComponentsPath } from '../../commands/utils/getUiBuilderComponentsPath';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../commands/utils/getUiBuilderComponentsPath', () => ({
|
|
7
|
+
...jest.requireActual('../../commands/utils/getUiBuilderComponentsPath'),
|
|
8
|
+
getUiBuilderComponentsPath: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock('path', () => ({
|
|
12
|
+
...jest.requireActual('path'),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const pathMocked = path as any;
|
|
16
|
+
|
|
17
|
+
const getUiBuilderComponentsPathMocked = getUiBuilderComponentsPath as any;
|
|
18
|
+
|
|
19
|
+
describe('relativeToComponentsPath', () => {
|
|
20
|
+
it('should return posix relative path when run in a windows-like environment', () => {
|
|
21
|
+
pathMocked.relative = path.win32.relative;
|
|
22
|
+
pathMocked.sep = path.win32.sep;
|
|
23
|
+
const projectPath = 'c:\\dev\\test\\test-project';
|
|
24
|
+
const toImport = projectPath + '\\src\\graphql\\queries.js';
|
|
25
|
+
getUiBuilderComponentsPathMocked.mockReturnValue(projectPath + '\\src\\ui-components');
|
|
26
|
+
|
|
27
|
+
const response = relativeToComponentsPath(toImport, {} as $TSContext);
|
|
28
|
+
|
|
29
|
+
expect(response).toBe('../graphql/queries.js');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return expected relative path', () => {
|
|
33
|
+
const projectPath = '/dev/test/test-project';
|
|
34
|
+
const toImport = projectPath + '/src/graphql/queries.js';
|
|
35
|
+
getUiBuilderComponentsPathMocked.mockReturnValue(projectPath + '/src/ui-components');
|
|
36
|
+
|
|
37
|
+
const response = relativeToComponentsPath(toImport, {} as $TSContext);
|
|
38
|
+
|
|
39
|
+
expect(response).toBe('../graphql/queries.js');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -17,6 +17,7 @@ export type StudioMetadata = {
|
|
|
17
17
|
isRelationshipSupported: boolean;
|
|
18
18
|
isNonModelSupported: boolean;
|
|
19
19
|
};
|
|
20
|
+
isGraphQLEnabled: boolean;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -122,6 +123,7 @@ export default class AmplifyStudioClient {
|
|
|
122
123
|
isRelationshipSupported: false,
|
|
123
124
|
isNonModelSupported: false,
|
|
124
125
|
},
|
|
126
|
+
isGraphQLEnabled: false,
|
|
125
127
|
};
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -145,6 +147,7 @@ export default class AmplifyStudioClient {
|
|
|
145
147
|
isRelationshipSupported: response.features?.isRelationshipSupported === 'true',
|
|
146
148
|
isNonModelSupported: response.features?.isNonModelSupported === 'true',
|
|
147
149
|
},
|
|
150
|
+
isGraphQLEnabled: response.features?.isGraphQLEnabled === 'true',
|
|
148
151
|
};
|
|
149
152
|
} catch (err) {
|
|
150
153
|
throw new Error(`Failed to load metadata: ${err.message}`);
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
hasStorageField,
|
|
13
13
|
mapGenericDataSchemaToCodegen,
|
|
14
14
|
waitForSucceededJob,
|
|
15
|
-
getUiBuilderComponentsPath,
|
|
16
15
|
extractUIComponents,
|
|
17
16
|
} from './utils';
|
|
17
|
+
import { getUiBuilderComponentsPath } from './utils/getUiBuilderComponentsPath';
|
|
18
18
|
import { AmplifyUIBuilder } from 'aws-sdk';
|
|
19
|
+
import { getApiConfiguration, hasDataStoreConfiguration, hasGraphQLConfiguration } from './utils/getApiConfiguration';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Pulls ui components from Studio backend and generates the code in the user's file system
|
|
@@ -34,10 +35,15 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull
|
|
|
34
35
|
studioClient.isGraphQLSupported ? getAmplifyDataSchema(context) : Promise.resolve(undefined),
|
|
35
36
|
]);
|
|
36
37
|
|
|
37
|
-
const
|
|
38
|
-
!dataSchema || !studioClient.metadata.autoGenerateForms || !studioClient.isGraphQLSupported || !studioClient.isDataStoreEnabled;
|
|
38
|
+
const canGenerateDataComponents = dataSchema && studioClient.isGraphQLSupported;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const apiConfiguration: AmplifyUIBuilder.ApiConfiguration = canGenerateDataComponents
|
|
41
|
+
? getApiConfiguration(studioClient, context)
|
|
42
|
+
: { noApiConfig: {} };
|
|
43
|
+
const hasDataAPI = hasDataStoreConfiguration(apiConfiguration) || hasGraphQLConfiguration(apiConfiguration);
|
|
44
|
+
const willAutogenerateItems = canGenerateDataComponents && studioClient.metadata.autoGenerateForms && hasDataAPI;
|
|
45
|
+
|
|
46
|
+
if (!willAutogenerateItems && [componentSchemas, themeSchemas, formSchemas].every((group) => !group.entities.length)) {
|
|
41
47
|
printer.debug('Skipping UI component generation since none are found.');
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
@@ -52,9 +58,10 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull
|
|
|
52
58
|
target: 'es2020',
|
|
53
59
|
script: 'jsx',
|
|
54
60
|
renderTypeDeclarations: true,
|
|
55
|
-
|
|
61
|
+
apiConfiguration,
|
|
62
|
+
} as AmplifyUIBuilder.ReactStartCodegenJobData,
|
|
56
63
|
},
|
|
57
|
-
autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported,
|
|
64
|
+
autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported && hasDataAPI,
|
|
58
65
|
features: studioClient.metadata.formFeatureFlags,
|
|
59
66
|
};
|
|
60
67
|
// SDK will throw if this is undefined
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { printer } from '@aws-amplify/amplify-prompts';
|
|
2
|
+
import { AmplifyStudioClient } from '../../clients';
|
|
3
|
+
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
4
|
+
import { getUiBuilderComponentsPath } from './getUiBuilderComponentsPath';
|
|
5
|
+
import { getCodegenConfig } from 'amplify-codegen';
|
|
6
|
+
import { ApiConfiguration } from 'aws-sdk/clients/amplifyuibuilder';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
//a posix formatted relative path must always be returned as the return values are used directly in jsx files as import paths
|
|
10
|
+
export function relativeToComponentsPath(importPath: string, context: $TSContext): string {
|
|
11
|
+
const componentsPath = getUiBuilderComponentsPath(context);
|
|
12
|
+
const segments = path.relative(componentsPath, importPath).split(path.sep);
|
|
13
|
+
return path.posix.join(...segments);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getApiConfiguration(studioClient: AmplifyStudioClient, context: $TSContext): ApiConfiguration {
|
|
17
|
+
if (studioClient.isDataStoreEnabled) {
|
|
18
|
+
return {
|
|
19
|
+
dataStoreConfig: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (studioClient.metadata.isGraphQLEnabled) {
|
|
24
|
+
printer.debug('building graphql config');
|
|
25
|
+
// attempt to get api codegen info
|
|
26
|
+
const projectPath = context.exeInfo.localEnvInfo.projectPath;
|
|
27
|
+
let promptForUpdateCodegen = false;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const codegenConfig = getCodegenConfig(projectPath);
|
|
31
|
+
const typesPath = codegenConfig.getGeneratedTypesPath();
|
|
32
|
+
const apiConfiguration = {
|
|
33
|
+
graphQLConfig: {
|
|
34
|
+
typesFilePath: (typesPath && relativeToComponentsPath(typesPath, context)) || '',
|
|
35
|
+
queriesFilePath: relativeToComponentsPath(codegenConfig.getGeneratedQueriesPath(), context),
|
|
36
|
+
mutationsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedMutationsPath(), context),
|
|
37
|
+
subscriptionsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedSubscriptionsPath(), context),
|
|
38
|
+
fragmentsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedFragmentsPath(), context),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const minQueryDepth = 3;
|
|
43
|
+
const isQueryingTooShallow = (codegenConfig.getQueryMaxDepth() || 0) < minQueryDepth;
|
|
44
|
+
|
|
45
|
+
if (studioClient.metadata.formFeatureFlags.isRelationshipSupported && isQueryingTooShallow) {
|
|
46
|
+
promptForUpdateCodegen = true;
|
|
47
|
+
printer.warn(`Forms with relationships require a maximum query depth of at least ${minQueryDepth}.`);
|
|
48
|
+
}
|
|
49
|
+
return apiConfiguration;
|
|
50
|
+
} catch {
|
|
51
|
+
promptForUpdateCodegen = true;
|
|
52
|
+
printer.warn(
|
|
53
|
+
'Unable to successfully configure component generation for GraphQL. This will impact generating forms and components bound to your data models.',
|
|
54
|
+
);
|
|
55
|
+
} finally {
|
|
56
|
+
if (promptForUpdateCodegen) {
|
|
57
|
+
printer.warn(`Run 'amplify update codegen' to ensure GraphQL configurations for your project are correct.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
noApiConfig: {},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function hasDataStoreConfiguration(apiConfiguration: ApiConfiguration): boolean {
|
|
68
|
+
return apiConfiguration.dataStoreConfig !== undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function hasGraphQLConfiguration(apiConfiguration: ApiConfiguration): boolean {
|
|
72
|
+
return apiConfiguration.graphQLConfig !== undefined;
|
|
73
|
+
}
|