@aws-amplify/amplify-util-uibuilder 1.8.5-rds.1 → 1.9.0-cdk228withdata.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 +3 -8
- package/lib/clients/amplify-studio-client.d.ts +4 -1
- package/lib/clients/amplify-studio-client.d.ts.map +1 -1
- package/lib/clients/amplify-studio-client.js +43 -3
- package/lib/clients/amplify-studio-client.js.map +1 -1
- package/lib/commands/generateComponents.d.ts.map +1 -1
- package/lib/commands/generateComponents.js +32 -33
- package/lib/commands/generateComponents.js.map +1 -1
- package/lib/commands/utils/codegenResources.d.ts +1 -15
- package/lib/commands/utils/codegenResources.d.ts.map +1 -1
- package/lib/commands/utils/codegenResources.js +1 -123
- package/lib/commands/utils/codegenResources.js.map +1 -1
- package/lib/commands/utils/detachedFormCleanup.d.ts +3 -4
- package/lib/commands/utils/detachedFormCleanup.d.ts.map +1 -1
- package/lib/commands/utils/detachedFormCleanup.js.map +1 -1
- package/lib/commands/utils/storageManager.d.ts +3 -4
- package/lib/commands/utils/storageManager.d.ts.map +1 -1
- package/lib/commands/utils/storageManager.js +1 -5
- package/lib/commands/utils/storageManager.js.map +1 -1
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts +8 -16
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts.map +1 -1
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.js +186 -74
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/__snapshots__/syncAmplifyUiComponents.test.ts.snap +46 -0
- package/src/__tests__/detachedFormCleanup.test.ts +7 -5
- package/src/__tests__/generateComponents.test.ts +101 -28
- package/src/__tests__/storageManager.test.ts +7 -4
- package/src/__tests__/syncAmplifyUiComponents.test.ts +98 -96
- package/src/clients/amplify-studio-client.ts +44 -4
- package/src/commands/generateComponents.ts +43 -55
- package/src/commands/utils/codegenResources.ts +1 -226
- package/src/commands/utils/detachedFormCleanup.ts +3 -4
- package/src/commands/utils/storageManager.ts +3 -7
- package/src/commands/utils/syncAmplifyUiBuilderComponents.ts +210 -123
- package/tsconfig.tsbuildinfo +1 -1
- package/src/__tests__/createUiBuilderComponent.test.ts +0 -90
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { StudioSchema } from '@aws-amplify/codegen-ui';
|
|
2
1
|
import { hasStorageField } from '../commands/utils';
|
|
2
|
+
import { Form } from 'aws-sdk/clients/amplifyuibuilder';
|
|
3
3
|
|
|
4
|
-
const formWithNoCustomization:
|
|
4
|
+
const formWithNoCustomization: Form = {
|
|
5
5
|
id: 'f-123456',
|
|
6
|
+
appId: '123',
|
|
7
|
+
environmentName: 'staging',
|
|
8
|
+
schemaVersion: '1.0',
|
|
6
9
|
name: 'BlogCreateForm',
|
|
7
10
|
formActionType: 'create',
|
|
8
11
|
dataType: {
|
|
@@ -15,7 +18,7 @@ const formWithNoCustomization: StudioSchema = {
|
|
|
15
18
|
cta: {},
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
const formWithNoStorageField:
|
|
21
|
+
const formWithNoStorageField: Form = {
|
|
19
22
|
...formWithNoCustomization,
|
|
20
23
|
dataType: { dataSourceType: 'Custom', dataTypeName: 'Blog' },
|
|
21
24
|
fields: {
|
|
@@ -23,7 +26,7 @@ const formWithNoStorageField: StudioSchema = {
|
|
|
23
26
|
},
|
|
24
27
|
};
|
|
25
28
|
|
|
26
|
-
const formWithStorageField:
|
|
29
|
+
const formWithStorageField: Form = {
|
|
27
30
|
...formWithNoCustomization,
|
|
28
31
|
dataType: { dataSourceType: 'Custom', dataTypeName: 'Blog' },
|
|
29
32
|
fields: {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import aws from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies
|
|
1
|
+
import { AmplifyCategories, AmplifySupportedService, stateManager } from '@aws-amplify/amplify-cli-core';
|
|
2
|
+
import aws from 'aws-sdk';
|
|
4
3
|
import {
|
|
5
|
-
generateUiBuilderComponents,
|
|
6
|
-
generateUiBuilderThemes,
|
|
7
4
|
getEnvName,
|
|
8
5
|
getAppId,
|
|
9
6
|
resolveAppId,
|
|
10
|
-
|
|
7
|
+
mapGenericDataSchemaToCodegen,
|
|
8
|
+
waitForSucceededJob,
|
|
9
|
+
extractUIComponents,
|
|
10
|
+
fetchWithRetries,
|
|
11
11
|
} from '../commands/utils';
|
|
12
12
|
import { AmplifyStudioClient } from '../clients';
|
|
13
|
-
import * as createUiBuilderComponentDependency from '../commands/utils/codegenResources';
|
|
14
|
-
import { exampleSchema } from './utils';
|
|
15
13
|
import { isDataStoreEnabled } from '@aws-amplify/amplify-category-api';
|
|
14
|
+
import type { GenericDataSchema } from '@aws-amplify/codegen-ui';
|
|
15
|
+
import fetch, { Response } from 'node-fetch';
|
|
16
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
16
17
|
|
|
17
18
|
jest.mock('@aws-amplify/amplify-cli-core', () => ({
|
|
18
19
|
...jest.requireActual('@aws-amplify/amplify-cli-core'),
|
|
@@ -24,21 +25,57 @@ jest.mock('@aws-amplify/amplify-cli-core', () => ({
|
|
|
24
25
|
jest.mock('../commands/utils/featureFlags', () => ({
|
|
25
26
|
getTransformerVersion: jest.fn().mockReturnValue(2),
|
|
26
27
|
}));
|
|
27
|
-
|
|
28
28
|
jest.mock('@aws-amplify/amplify-category-api', () => ({
|
|
29
29
|
...jest.requireActual('@aws-amplify/amplify-category-api'),
|
|
30
30
|
isDataStoreEnabled: jest.fn(),
|
|
31
31
|
}));
|
|
32
|
+
jest.mock('fs');
|
|
33
|
+
jest.mock('node-fetch');
|
|
32
34
|
|
|
33
35
|
const awsMock = aws as any;
|
|
34
36
|
const stateManagerMock = stateManager as any;
|
|
35
|
-
const createUiBuilderComponentDependencyMock = createUiBuilderComponentDependency as any;
|
|
36
37
|
const isDataStoreEnabledMocked = jest.mocked(isDataStoreEnabled);
|
|
38
|
+
const mockWriteFileSync = jest.mocked(writeFileSync);
|
|
39
|
+
const mockExistsSync = jest.mocked(existsSync);
|
|
40
|
+
const mockNodeFetch = jest.mocked(fetch);
|
|
37
41
|
|
|
38
42
|
describe('should sync amplify ui builder components', () => {
|
|
39
43
|
let context: any;
|
|
44
|
+
const env = process.env;
|
|
45
|
+
|
|
46
|
+
const mockGenericDataSchema: GenericDataSchema = {
|
|
47
|
+
dataSourceType: 'DataStore',
|
|
48
|
+
models: {
|
|
49
|
+
Blog: {
|
|
50
|
+
fields: {
|
|
51
|
+
id: { dataType: 'ID', required: true, readOnly: false, isArray: false },
|
|
52
|
+
},
|
|
53
|
+
primaryKeys: ['id'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
enums: {
|
|
57
|
+
Status: { values: ['ENABLED', 'DISABLED', 'HIDDEN'] },
|
|
58
|
+
},
|
|
59
|
+
nonModels: {
|
|
60
|
+
Metadata: {
|
|
61
|
+
fields: {
|
|
62
|
+
name: {
|
|
63
|
+
dataType: 'String',
|
|
64
|
+
required: false,
|
|
65
|
+
readOnly: false,
|
|
66
|
+
isArray: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
40
73
|
beforeEach(() => {
|
|
74
|
+
mockNodeFetch.mockReset();
|
|
75
|
+
process.env = { ...env };
|
|
76
|
+
|
|
41
77
|
isDataStoreEnabledMocked.mockResolvedValue(true);
|
|
78
|
+
mockExistsSync.mockReturnValue(false);
|
|
42
79
|
context = {
|
|
43
80
|
exeInfo: {
|
|
44
81
|
projectConfig: {
|
|
@@ -156,29 +193,10 @@ describe('should sync amplify ui builder components', () => {
|
|
|
156
193
|
})),
|
|
157
194
|
})),
|
|
158
195
|
}));
|
|
159
|
-
|
|
160
|
-
createUiBuilderComponentDependencyMock.createUiBuilderComponent = jest.fn();
|
|
161
|
-
createUiBuilderComponentDependencyMock.createUiBuilderTheme = jest.fn();
|
|
162
|
-
createUiBuilderComponentDependencyMock.createUiBuilderForm = jest.fn();
|
|
163
|
-
createUiBuilderComponentDependencyMock.createUiBuilderView = jest.fn();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('pulls components from aws-sdk and passes them to createUiBuilderComponent', () => {
|
|
167
|
-
generateUiBuilderComponents(context, []);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('does not throw an error when createUiBuilderComponent fails', () => {
|
|
171
|
-
createUiBuilderComponentDependencyMock.createUiBuilderComponent = jest.fn(() => {
|
|
172
|
-
throw new Error('ahhh!'); // eslint-disable-line spellcheck/spell-checker
|
|
173
|
-
});
|
|
174
|
-
expect(async () => generateUiBuilderComponents(context, [])).not.toThrow();
|
|
175
196
|
});
|
|
176
197
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
throw new Error('ahhh!'); // eslint-disable-line spellcheck/spell-checker
|
|
180
|
-
});
|
|
181
|
-
expect(async () => generateUiBuilderThemes(context, [])).not.toThrow();
|
|
198
|
+
afterEach(() => {
|
|
199
|
+
process.env = env;
|
|
182
200
|
});
|
|
183
201
|
|
|
184
202
|
it('can getAmplifyUIBuilderService', async () => {
|
|
@@ -199,110 +217,94 @@ describe('should sync amplify ui builder components', () => {
|
|
|
199
217
|
}),
|
|
200
218
|
);
|
|
201
219
|
});
|
|
220
|
+
|
|
202
221
|
it('can list components', async () => {
|
|
203
222
|
const client = await AmplifyStudioClient.setClientInfo(context);
|
|
204
223
|
const components = await client.listComponents();
|
|
205
224
|
expect(components.entities).toHaveLength(1);
|
|
206
225
|
});
|
|
226
|
+
|
|
207
227
|
it('can list themes', async () => {
|
|
208
228
|
const client = await AmplifyStudioClient.setClientInfo(context);
|
|
209
229
|
const themes = await client.listThemes();
|
|
210
230
|
expect(themes.entities).toHaveLength(1);
|
|
211
231
|
});
|
|
232
|
+
|
|
212
233
|
it('can list forms', async () => {
|
|
213
234
|
const client = await AmplifyStudioClient.setClientInfo(context);
|
|
214
235
|
const forms = await client.listForms();
|
|
215
236
|
expect(forms.entities).toHaveLength(1);
|
|
216
237
|
});
|
|
238
|
+
|
|
217
239
|
it('can getAppId', async () => {
|
|
218
240
|
const appId = getAppId(context);
|
|
219
241
|
expect(appId).toBe('testAppId');
|
|
220
242
|
});
|
|
243
|
+
|
|
221
244
|
it('can getEnvName', () => {
|
|
222
245
|
const envName = getEnvName(context);
|
|
223
246
|
expect(envName).toBe('testEnvName');
|
|
224
247
|
});
|
|
248
|
+
|
|
225
249
|
it('can resolveAppId', async () => {
|
|
226
250
|
const appId = resolveAppId();
|
|
227
251
|
expect(appId).toBe('testAppId');
|
|
228
252
|
});
|
|
253
|
+
|
|
229
254
|
it('can throw on getAppId', async () => {
|
|
230
255
|
context.input.options.appId = null;
|
|
231
256
|
context.amplify.invokePluginMethod = () => null;
|
|
232
257
|
stateManagerMock.getMeta = () => ({});
|
|
233
258
|
expect(() => getAppId(context)).toThrowError();
|
|
234
259
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
expect(components.every((component) => component.resultType === 'SUCCESS')).toBeTruthy();
|
|
239
|
-
});
|
|
240
|
-
it('can handle failed generation generate ui builder components', async () => {
|
|
241
|
-
createUiBuilderComponentDependencyMock.createUiBuilderComponent = jest.fn().mockImplementation(() => {
|
|
242
|
-
throw new Error('ahh!');
|
|
243
|
-
});
|
|
244
|
-
const components = generateUiBuilderComponents(context, [{}, {}]);
|
|
245
|
-
expect(components.every((component) => component.resultType === 'FAILURE')).toBeTruthy();
|
|
246
|
-
});
|
|
247
|
-
it('can generate ui builder themes', async () => {
|
|
248
|
-
createUiBuilderComponentDependencyMock.createUiBuilderTheme = jest.fn().mockImplementation(() => ({}));
|
|
249
|
-
const themes = generateUiBuilderThemes(context, [{}, {}]);
|
|
250
|
-
expect(themes.every((theme) => theme.resultType === 'SUCCESS')).toBeTruthy();
|
|
251
|
-
});
|
|
252
|
-
it('can handle failed generation generate ui builder themes', async () => {
|
|
253
|
-
createUiBuilderComponentDependencyMock.createUiBuilderTheme = jest.fn().mockImplementation(() => {
|
|
254
|
-
throw new Error('ahh!');
|
|
255
|
-
});
|
|
256
|
-
const themes = generateUiBuilderThemes(context, [{}, {}]);
|
|
257
|
-
expect(themes.every((theme) => theme.resultType === 'FAILURE')).toBeTruthy();
|
|
258
|
-
});
|
|
259
|
-
it('can generate ui builder default theme when no themes are passed', async () => {
|
|
260
|
-
createUiBuilderComponentDependencyMock.createUiBuilderTheme = jest.fn().mockImplementation(() => ({}));
|
|
261
|
-
const themes = generateUiBuilderThemes(context, []);
|
|
262
|
-
expect(themes.every((theme) => theme.resultType === 'SUCCESS')).toBeTruthy();
|
|
263
|
-
});
|
|
264
|
-
it('can handle failed generation generate ui builder default theme when no themes are passed', async () => {
|
|
265
|
-
createUiBuilderComponentDependencyMock.createUiBuilderTheme = jest.fn().mockImplementation(() => {
|
|
266
|
-
throw new Error('ahh!');
|
|
267
|
-
});
|
|
268
|
-
const themes = generateUiBuilderThemes(context, []);
|
|
269
|
-
expect(themes.every((theme) => theme.resultType === 'FAILURE')).toBeTruthy();
|
|
260
|
+
|
|
261
|
+
it('can map generic data schema to start codegen job request', () => {
|
|
262
|
+
expect(mapGenericDataSchemaToCodegen(mockGenericDataSchema)).toMatchSnapshot();
|
|
270
263
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
264
|
+
|
|
265
|
+
it('can wait for a succeeded job', async () => {
|
|
266
|
+
const succeededJob = { status: 'succeeded' };
|
|
267
|
+
const getJob = jest.fn().mockResolvedValueOnce({ status: 'in_progress' }).mockResolvedValueOnce(succeededJob);
|
|
268
|
+
const job = await waitForSucceededJob(getJob, { pollInterval: 1 });
|
|
269
|
+
expect(job).toEqual(succeededJob);
|
|
275
270
|
});
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
expect(forms.every((form) => form.resultType === 'FAILURE')).toBeTruthy();
|
|
271
|
+
|
|
272
|
+
it('can throw if job failed', async () => {
|
|
273
|
+
const statusMessage = 'failed status';
|
|
274
|
+
const getJob = jest.fn().mockResolvedValueOnce({ status: 'in_progress' }).mockResolvedValueOnce({ status: 'failed', statusMessage });
|
|
275
|
+
await expect(waitForSucceededJob(getJob, { pollInterval: 2 })).rejects.toThrow(statusMessage);
|
|
282
276
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
expect(
|
|
277
|
+
|
|
278
|
+
it('can throw after timeout when waiting for a succeeded job', async () => {
|
|
279
|
+
process.env.UI_BUILDER_CODEGENJOB_TIMEOUT = '1';
|
|
280
|
+
const succeededJob = { status: 'succeeded' };
|
|
281
|
+
const getJob = jest.fn().mockResolvedValueOnce({ status: 'in_progress' }).mockResolvedValueOnce(succeededJob);
|
|
282
|
+
await expect(waitForSucceededJob(getJob, { pollInterval: 2 })).rejects.toThrow('Failed to return codegen job');
|
|
289
283
|
});
|
|
290
284
|
|
|
291
|
-
it('
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
285
|
+
it('can extract ui components from archive', async () => {
|
|
286
|
+
const mockedFetchResponse = {
|
|
287
|
+
ok: true,
|
|
288
|
+
json: jest.fn().mockResolvedValue({
|
|
289
|
+
Output: [
|
|
290
|
+
{
|
|
291
|
+
downloadUrl: 'https://s3.com',
|
|
292
|
+
fileName: 'MyComponent.jsx',
|
|
293
|
+
schemaName: 'MyComponent',
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
}),
|
|
297
|
+
text: jest.fn().mockResolvedValue('source code'),
|
|
298
|
+
} as unknown as Response;
|
|
299
|
+
mockNodeFetch.mockResolvedValue(mockedFetchResponse);
|
|
300
|
+
mockExistsSync.mockReturnValue(true);
|
|
301
|
+
await extractUIComponents('https:://example.aws', 'tmp/ui-components');
|
|
302
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith('tmp/ui-components/MyComponent.jsx', 'source code');
|
|
301
303
|
});
|
|
302
304
|
|
|
303
|
-
it('
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
expect(
|
|
305
|
+
it('fetchWithRetries retries 3 times', async () => {
|
|
306
|
+
mockNodeFetch.mockRejectedValue(new Error('Server dropped the connection'));
|
|
307
|
+
await expect(fetchWithRetries('https://amazon.com')).rejects.toThrowError('Fetch reached max number of retries without succeeding');
|
|
308
|
+
expect(mockNodeFetch).toHaveBeenCalledTimes(3);
|
|
307
309
|
});
|
|
308
310
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $TSContext, CloudformationProviderFacade } from '@aws-amplify/amplify-cli-core';
|
|
2
2
|
import type { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
|
|
3
|
-
import type { CreateComponentData, Component, Theme, Form } from 'aws-sdk/clients/amplifyuibuilder';
|
|
3
|
+
import type { CreateComponentData, Component, Theme, Form, StartCodegenJobData } from 'aws-sdk/clients/amplifyuibuilder';
|
|
4
4
|
import { AmplifyUIBuilder, AmplifyBackend } from 'aws-sdk';
|
|
5
5
|
import { printer } from '@aws-amplify/amplify-prompts';
|
|
6
6
|
import { getAppId, getEnvName } from '../commands/utils/environmentHelpers';
|
|
@@ -171,7 +171,6 @@ export default class AmplifyStudioClient {
|
|
|
171
171
|
uiBuilderComponents.push(...response.entities);
|
|
172
172
|
nextToken = response.nextToken;
|
|
173
173
|
} while (nextToken);
|
|
174
|
-
printer.debug(JSON.stringify(uiBuilderComponents, null, 2));
|
|
175
174
|
return { entities: uiBuilderComponents };
|
|
176
175
|
} catch (err) {
|
|
177
176
|
throw new Error(`Failed to list components: ${err.message}`);
|
|
@@ -199,7 +198,6 @@ export default class AmplifyStudioClient {
|
|
|
199
198
|
uiBuilderThemes.push(...response.entities);
|
|
200
199
|
nextToken = response.nextToken;
|
|
201
200
|
} while (nextToken);
|
|
202
|
-
printer.debug(JSON.stringify(uiBuilderThemes, null, 2));
|
|
203
201
|
return { entities: uiBuilderThemes };
|
|
204
202
|
} catch (err) {
|
|
205
203
|
throw new Error(`Failed to list themes: ${err.message}`);
|
|
@@ -223,7 +221,6 @@ export default class AmplifyStudioClient {
|
|
|
223
221
|
uibuilderForms.push(...response.entities);
|
|
224
222
|
nextToken = response.nextToken;
|
|
225
223
|
} while (nextToken);
|
|
226
|
-
printer.debug(JSON.stringify(uibuilderForms, null, 2));
|
|
227
224
|
return { entities: uibuilderForms };
|
|
228
225
|
} catch (err) {
|
|
229
226
|
throw new Error(`Failed to list forms: ${err.message}`);
|
|
@@ -274,4 +271,47 @@ export default class AmplifyStudioClient {
|
|
|
274
271
|
throw new Error(`Models not found in AmplifyBackend:GetBackendAPIModels response: ${err.message}`);
|
|
275
272
|
}
|
|
276
273
|
};
|
|
274
|
+
|
|
275
|
+
startCodegenJob = async (codegenJobToCreate: StartCodegenJobData, appId?: string, envName?: string) => {
|
|
276
|
+
const environmentName = envName || this.#envName;
|
|
277
|
+
const resolvedAppId = appId || this.#appId;
|
|
278
|
+
try {
|
|
279
|
+
const response = await this.#amplifyUiBuilder
|
|
280
|
+
.startCodegenJob({
|
|
281
|
+
appId: resolvedAppId,
|
|
282
|
+
environmentName,
|
|
283
|
+
codegenJobToCreate,
|
|
284
|
+
})
|
|
285
|
+
.promise();
|
|
286
|
+
if (!response.entity || !response.entity.id) {
|
|
287
|
+
throw new Error('Error starting codegen job');
|
|
288
|
+
}
|
|
289
|
+
return response.entity.id;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
printer.debug('Failed to start job');
|
|
292
|
+
printer.debug(err.stack);
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
getCodegenJob = async (jobId: string, appId?: string, envName?: string) => {
|
|
298
|
+
const environmentName = envName || this.#envName;
|
|
299
|
+
const resolvedAppId = appId || this.#appId;
|
|
300
|
+
try {
|
|
301
|
+
const { job } = await this.#amplifyUiBuilder
|
|
302
|
+
.getCodegenJob({
|
|
303
|
+
id: jobId,
|
|
304
|
+
appId: resolvedAppId,
|
|
305
|
+
environmentName,
|
|
306
|
+
})
|
|
307
|
+
.promise();
|
|
308
|
+
if (!job) {
|
|
309
|
+
throw new Error('Error getting codegen job');
|
|
310
|
+
}
|
|
311
|
+
return job;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
printer.debug(err.toString());
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
277
317
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable spellcheck/spell-checker */
|
|
2
|
-
import { StudioSchema } from '@aws-amplify/codegen-ui';
|
|
3
2
|
import ora from 'ora';
|
|
4
3
|
import { printer } from '@aws-amplify/amplify-prompts';
|
|
5
4
|
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
@@ -7,18 +6,16 @@ import { AmplifyStudioClient } from '../clients';
|
|
|
7
6
|
import {
|
|
8
7
|
notifyMissingPackages,
|
|
9
8
|
shouldRenderComponents,
|
|
10
|
-
generateUiBuilderComponents,
|
|
11
|
-
generateUiBuilderThemes,
|
|
12
9
|
getAmplifyDataSchema,
|
|
13
|
-
generateAmplifyUiBuilderIndexFile,
|
|
14
|
-
generateUiBuilderForms,
|
|
15
|
-
generateAmplifyUiBuilderUtilFile,
|
|
16
|
-
isStudioForm,
|
|
17
10
|
isFormDetachedFromModel,
|
|
18
11
|
deleteDetachedForms,
|
|
19
12
|
hasStorageField,
|
|
20
|
-
|
|
13
|
+
mapGenericDataSchemaToCodegen,
|
|
14
|
+
waitForSucceededJob,
|
|
15
|
+
getUiBuilderComponentsPath,
|
|
16
|
+
extractUIComponents,
|
|
21
17
|
} from './utils';
|
|
18
|
+
import { AmplifyUIBuilder } from 'aws-sdk';
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Pulls ui components from Studio backend and generates the code in the user's file system
|
|
@@ -46,61 +43,51 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull
|
|
|
46
43
|
}
|
|
47
44
|
spinner.start('Generating UI components...');
|
|
48
45
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
const genericDataSchema = dataSchema ? mapGenericDataSchemaToCodegen(dataSchema) : undefined;
|
|
47
|
+
|
|
48
|
+
const job: AmplifyUIBuilder.StartCodegenJobData = {
|
|
49
|
+
renderConfig: {
|
|
50
|
+
react: {
|
|
51
|
+
module: 'es2020',
|
|
52
|
+
target: 'es2020',
|
|
53
|
+
script: 'jsx',
|
|
54
|
+
renderTypeDeclarations: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported,
|
|
58
|
+
features: studioClient.metadata.formFeatureFlags,
|
|
59
59
|
};
|
|
60
|
+
// SDK will throw if this is undefined
|
|
61
|
+
// even though it's optional
|
|
62
|
+
if (genericDataSchema) {
|
|
63
|
+
job.genericDataSchema = genericDataSchema;
|
|
64
|
+
}
|
|
65
|
+
const jobId = await studioClient.startCodegenJob(job);
|
|
66
|
+
const finishedJob = await waitForSucceededJob(() => studioClient.getCodegenJob(jobId), { pollInterval: 2000 });
|
|
67
|
+
if (!finishedJob.asset?.downloadUrl) {
|
|
68
|
+
throw new Error('No manifest file in codegen job');
|
|
69
|
+
}
|
|
70
|
+
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
71
|
+
await extractUIComponents(finishedJob.asset.downloadUrl, uiBuilderComponentsPath);
|
|
60
72
|
|
|
61
|
-
const successfulSchemas: StudioSchema[] = [];
|
|
62
73
|
const detachedForms: { id: string; name: string }[] = [];
|
|
63
|
-
let hasSuccessfulForm = false;
|
|
64
74
|
const failedResponseNames: string[] = [];
|
|
65
75
|
const modelNames = dataSchema?.models ? new Set(Object.keys(dataSchema.models)) : new Set<string>();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Object.entries(generatedResults).forEach(([key, results]) => {
|
|
69
|
-
results.forEach((result) => {
|
|
70
|
-
if (result.resultType === 'SUCCESS') {
|
|
71
|
-
successfulSchemas.push(result.schema);
|
|
72
|
-
if (key === 'form') {
|
|
73
|
-
hasSuccessfulForm = true;
|
|
76
|
+
const hasStorageManagerField = formSchemas.entities.some((formSchema) => hasStorageField(formSchema));
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
const failedSchema = result.schema;
|
|
81
|
-
/**
|
|
82
|
-
* A form resource may fail to generate because it's DataStore model type
|
|
83
|
-
* no longer exists.
|
|
84
|
-
*/
|
|
85
|
-
if (
|
|
86
|
-
isStudioForm(failedSchema) &&
|
|
87
|
-
failedSchema.id &&
|
|
88
|
-
dataSchema &&
|
|
89
|
-
eventType === 'PostPush' &&
|
|
90
|
-
isFormDetachedFromModel(failedSchema, modelNames)
|
|
91
|
-
) {
|
|
92
|
-
// Don't need to add form to failedResponseNames if it is going to be deleted
|
|
93
|
-
detachedForms.push({ id: failedSchema.id, name: failedSchema.name });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
failedResponseNames.push(result.schemaName);
|
|
97
|
-
}
|
|
78
|
+
if (dataSchema && eventType === 'PostPush') {
|
|
79
|
+
formSchemas.entities.forEach((formSchema) => {
|
|
80
|
+
isFormDetachedFromModel(formSchema, modelNames) && detachedForms.push({ id: formSchema.id, name: formSchema.name });
|
|
98
81
|
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
generateAmplifyUiBuilderIndexFile(context, successfulSchemas);
|
|
82
|
+
}
|
|
102
83
|
|
|
103
|
-
|
|
84
|
+
if (finishedJob.statusMessage) {
|
|
85
|
+
const errorStack = JSON.parse(finishedJob.statusMessage)?.codegenErrors as {
|
|
86
|
+
schemaName: string;
|
|
87
|
+
error: string;
|
|
88
|
+
}[];
|
|
89
|
+
errorStack.forEach((e) => failedResponseNames.push(e.schemaName));
|
|
90
|
+
}
|
|
104
91
|
|
|
105
92
|
if (failedResponseNames.length > 0) {
|
|
106
93
|
spinner.fail(`Failed to sync the following components: ${failedResponseNames.join(', ')}`);
|
|
@@ -112,6 +99,7 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull
|
|
|
112
99
|
...componentSchemas.entities.filter((component) => !component.schemaVersion).map((component) => component.name),
|
|
113
100
|
...formSchemas.entities.filter((form) => !form.schemaVersion).map((form) => form.name),
|
|
114
101
|
];
|
|
102
|
+
|
|
115
103
|
if (invalidComponentNames.length) {
|
|
116
104
|
printer.warn(
|
|
117
105
|
`The components ${invalidComponentNames.join(
|