@aws-amplify/amplify-util-uibuilder 1.5.0 → 1.6.0-beta.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 (69) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/amplify-plugin.json +10 -3
  3. package/lib/clients/amplify-studio-client.d.ts +13 -2
  4. package/lib/clients/amplify-studio-client.d.ts.map +1 -1
  5. package/lib/clients/amplify-studio-client.js +85 -17
  6. package/lib/clients/amplify-studio-client.js.map +1 -1
  7. package/lib/commands/generateComponents.d.ts.map +1 -1
  8. package/lib/commands/generateComponents.js +33 -15
  9. package/lib/commands/generateComponents.js.map +1 -1
  10. package/lib/commands/help.d.ts +1 -1
  11. package/lib/commands/help.d.ts.map +1 -1
  12. package/lib/commands/help.js +2 -2
  13. package/lib/commands/help.js.map +1 -1
  14. package/lib/commands/utils/codegenResources.d.ts +14 -3
  15. package/lib/commands/utils/codegenResources.d.ts.map +1 -1
  16. package/lib/commands/utils/codegenResources.js +72 -5
  17. package/lib/commands/utils/codegenResources.js.map +1 -1
  18. package/lib/commands/utils/featureFlags.d.ts +2 -0
  19. package/lib/commands/utils/featureFlags.d.ts.map +1 -0
  20. package/lib/commands/utils/featureFlags.js +35 -0
  21. package/lib/commands/utils/featureFlags.js.map +1 -0
  22. package/lib/commands/utils/getUiBuilderComponentsPath.d.ts.map +1 -1
  23. package/lib/commands/utils/getUiBuilderComponentsPath.js.map +1 -1
  24. package/lib/commands/utils/shouldRenderComponents.d.ts +1 -1
  25. package/lib/commands/utils/shouldRenderComponents.d.ts.map +1 -1
  26. package/lib/commands/utils/shouldRenderComponents.js +14 -8
  27. package/lib/commands/utils/shouldRenderComponents.js.map +1 -1
  28. package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts +12 -20
  29. package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts.map +1 -1
  30. package/lib/commands/utils/syncAmplifyUiBuilderComponents.js +51 -3
  31. package/lib/commands/utils/syncAmplifyUiBuilderComponents.js.map +1 -1
  32. package/lib/commands/version.d.ts +1 -1
  33. package/lib/commands/version.d.ts.map +1 -1
  34. package/lib/commands/version.js +2 -2
  35. package/lib/commands/version.js.map +1 -1
  36. package/lib/event-handlers/handle-PostEnvAdd.d.ts +1 -1
  37. package/lib/event-handlers/handle-PostEnvAdd.d.ts.map +1 -1
  38. package/lib/event-handlers/handle-PostEnvAdd.js +4 -6
  39. package/lib/event-handlers/handle-PostEnvAdd.js.map +1 -1
  40. package/lib/event-handlers/handle-PostPull.d.ts +1 -1
  41. package/lib/event-handlers/handle-PostPull.d.ts.map +1 -1
  42. package/lib/event-handlers/handle-PostPull.js +4 -3
  43. package/lib/event-handlers/handle-PostPull.js.map +1 -1
  44. package/lib/event-handlers/handle-PostPush.d.ts +3 -0
  45. package/lib/event-handlers/handle-PostPush.d.ts.map +1 -0
  46. package/lib/event-handlers/handle-PostPush.js +15 -0
  47. package/lib/event-handlers/handle-PostPush.js.map +1 -0
  48. package/package.json +8 -7
  49. package/src/__tests__/cloneComponentsFromEnv.test.ts +17 -6
  50. package/src/__tests__/createUiBuilderComponent.test.ts +26 -3
  51. package/src/__tests__/generateComponents.test.ts +44 -15
  52. package/src/__tests__/getAmplifyDataSchema.test.ts +51 -19
  53. package/src/__tests__/shouldRenderComponents.test.ts +51 -4
  54. package/src/__tests__/syncAmplifyUiComponents.test.ts +126 -8
  55. package/src/__tests__/utils.ts +70 -0
  56. package/src/clients/amplify-studio-client.ts +157 -69
  57. package/src/commands/generateComponents.ts +43 -16
  58. package/src/commands/help.ts +5 -2
  59. package/src/commands/utils/codegenResources.ts +115 -6
  60. package/src/commands/utils/featureFlags.ts +62 -0
  61. package/src/commands/utils/getUiBuilderComponentsPath.ts +7 -2
  62. package/src/commands/utils/shouldRenderComponents.ts +20 -8
  63. package/src/commands/utils/syncAmplifyUiBuilderComponents.ts +96 -29
  64. package/src/commands/version.ts +5 -2
  65. package/src/event-handlers/handle-PostEnvAdd.ts +8 -6
  66. package/src/event-handlers/handle-PostPull.ts +7 -3
  67. package/src/event-handlers/handle-PostPush.ts +15 -0
  68. package/tsconfig.tsbuildinfo +1 -1
  69. package/src/__tests__/handleEvents.test.ts +0 -21
@@ -1,9 +1,20 @@
1
- import type { $TSContext } from 'amplify-cli-core';
1
+ import { $TSContext, CloudformationProviderFacade } from 'amplify-cli-core';
2
2
  import type { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
3
- import type { CreateComponentData, Component, Theme } from 'aws-sdk/clients/amplifyuibuilder';
3
+ import type {
4
+ CreateComponentData, Component, Theme, Form,
5
+ } from 'aws-sdk/clients/amplifyuibuilder';
4
6
  import { AmplifyUIBuilder, AmplifyBackend } from 'aws-sdk';
5
7
  import { printer } from 'amplify-prompts';
6
8
  import { getAppId, getEnvName } from '../commands/utils/environmentHelpers';
9
+ import { getTransformerVersion } from '../commands/utils/featureFlags';
10
+
11
+ /**
12
+ * studio client metadata
13
+ */
14
+ export type StudioMetadata = {
15
+ autoGenerateForms: boolean;
16
+ autoGenerateViews: boolean;
17
+ };
7
18
 
8
19
  /**
9
20
  * Builds the Amplify Backend Client
@@ -45,6 +56,24 @@ export default class AmplifyStudioClient {
45
56
  #amplifyBackend: AmplifyBackend;
46
57
  #appId: string;
47
58
  #envName: string;
59
+ metadata: StudioMetadata;
60
+ isGraphQLSupported = false;
61
+
62
+ /**
63
+ * static function meant to check if given appId is studio enabled
64
+ */
65
+ static isAmplifyApp = async (context: $TSContext, appId: string): Promise<boolean> => {
66
+ try {
67
+ const { isAdminApp } = await CloudformationProviderFacade.isAmplifyAdminApp(context, appId);
68
+ return isAdminApp;
69
+ } catch (err) {
70
+ // return false is admin app failed check
71
+ // this means we wont run codegen-ui
72
+ printer.debug(`Failed admin app check: ${err.message}`);
73
+ return false;
74
+ }
75
+ }
76
+
48
77
  /**
49
78
  * Used to configure the AWS Amplify clients.
50
79
  */
@@ -56,19 +85,55 @@ export default class AmplifyStudioClient {
56
85
  resolvedEnvName,
57
86
  resolvedAppId,
58
87
  ])) as ServiceConfigurationOptions;
59
- return new AmplifyStudioClient(awsConfigInfo, resolvedAppId, resolvedEnvName);
88
+
89
+ const client = new AmplifyStudioClient(awsConfigInfo, resolvedAppId, resolvedEnvName);
90
+
91
+ if ((await getTransformerVersion()) === 2) {
92
+ await client.loadMetadata();
93
+ client.isGraphQLSupported = true;
94
+ } else {
95
+ client.isGraphQLSupported = false;
96
+ }
97
+
98
+ return client;
60
99
  }
61
100
 
62
101
  /*
63
- * Builds the clients
64
- */
102
+ * Builds the clients
103
+ */
65
104
  constructor(awsConfigInfo: ServiceConfigurationOptions, appId: string, envName: string) {
66
105
  this.#amplifyUiBuilder = buildAmplifyUiBuilderClient(awsConfigInfo);
67
106
  this.#amplifyBackend = buildAmplifyBackendClient(awsConfigInfo);
68
107
  this.#appId = appId;
69
108
  this.#envName = envName;
109
+ this.metadata = {
110
+ autoGenerateForms: false,
111
+ autoGenerateViews: false,
112
+ };
70
113
  }
71
114
 
115
+ /**
116
+ * loads metadata from appId/envName
117
+ */
118
+ loadMetadata = async (envName?: string, appId?: string): Promise<void> => {
119
+ const environmentName = envName || this.#envName;
120
+ const resolvedAppId = appId || this.#appId;
121
+ try {
122
+ const response = await this.#amplifyUiBuilder
123
+ .getMetadata({
124
+ appId: resolvedAppId,
125
+ environmentName,
126
+ })
127
+ .promise();
128
+ this.metadata = {
129
+ autoGenerateForms: response.features?.autoGenerateForms === 'true',
130
+ autoGenerateViews: response.features?.autoGenerateViews === 'true',
131
+ };
132
+ } catch (err) {
133
+ throw new Error(`Failed to load metadata: ${err.message}`);
134
+ }
135
+ };
136
+
72
137
  /**
73
138
  * listComponents from Studio
74
139
  */
@@ -91,71 +156,94 @@ export default class AmplifyStudioClient {
91
156
  } while (nextToken);
92
157
  printer.debug(JSON.stringify(uiBuilderComponents, null, 2));
93
158
  return { entities: uiBuilderComponents };
94
- } catch (e) {
95
- printer.debug(e);
96
- throw e;
159
+ } catch (err) {
160
+ throw new Error(`Failed to list components: ${err.message}`);
97
161
  }
98
- }
162
+ };
99
163
 
100
164
  /**
101
- * Returns all the UI Builder themes from the app
102
- */
103
- listThemes = async (envName?: string, appId?: string): Promise<{ entities: Theme[] }> => {
104
- const environmentName = envName || this.#envName;
105
- const resolvedAppId = appId || this.#appId;
106
-
107
- try {
108
- let nextToken: string | undefined;
109
- const uiBuilderThemes: Theme[] = [];
110
- do {
111
- const response = await this.#amplifyUiBuilder
112
- .exportThemes({
113
- appId: resolvedAppId,
114
- environmentName,
115
- nextToken,
116
- })
117
- .promise();
118
- uiBuilderThemes.push(...response.entities);
119
- nextToken = response.nextToken;
120
- } while (nextToken);
121
- printer.debug(JSON.stringify(uiBuilderThemes, null, 2));
122
- return { entities: uiBuilderThemes };
123
- } catch (e) {
124
- printer.debug(e);
125
- throw e;
126
- }
127
- }
128
-
129
- createComponent = async (component: CreateComponentData, envName?: string, appId?: string): Promise<Component | undefined> => {
130
- const environmentName = envName || this.#envName;
131
- const resolvedAppId = appId || this.#appId;
132
- try {
133
- const response = await this.#amplifyUiBuilder.createComponent({
134
- appId: resolvedAppId,
135
- environmentName,
136
- componentToCreate: component,
137
- }).promise();
138
- return response.entity;
139
- } catch (err) {
140
- printer.debug(err.toString());
141
- throw err;
142
- }
143
- }
144
-
145
- getModels = async (resourceName: string, envName?: string, appId?: string): Promise<string | undefined> => {
146
- try {
147
- const environmentName = envName || this.#envName;
148
- const resolvedAppId = appId || this.#appId;
149
- const { Models } = await this.#amplifyBackend.getBackendAPIModels({
150
- AppId: resolvedAppId,
151
- BackendEnvironmentName: environmentName,
152
- ResourceName: resourceName,
153
- })
154
- .promise();
155
- return Models;
156
- } catch (e) {
157
- printer.debug(e);
158
- throw new Error(`Models not found in AmplifyBackend:GetBackendAPIModels response: ${e.message}`);
159
- }
160
- }
165
+ * Returns all the UI Builder themes from the app
166
+ */
167
+ listThemes = async (envName?: string, appId?: string): Promise<{ entities: Theme[] }> => {
168
+ const environmentName = envName || this.#envName;
169
+ const resolvedAppId = appId || this.#appId;
170
+
171
+ try {
172
+ let nextToken: string | undefined;
173
+ const uiBuilderThemes: Theme[] = [];
174
+ do {
175
+ const response = await this.#amplifyUiBuilder
176
+ .exportThemes({
177
+ appId: resolvedAppId,
178
+ environmentName,
179
+ nextToken,
180
+ })
181
+ .promise();
182
+ uiBuilderThemes.push(...response.entities);
183
+ nextToken = response.nextToken;
184
+ } while (nextToken);
185
+ printer.debug(JSON.stringify(uiBuilderThemes, null, 2));
186
+ return { entities: uiBuilderThemes };
187
+ } catch (err) {
188
+ throw new Error(`Failed to list themes: ${err.message}`);
189
+ }
190
+ };
191
+
192
+ listForms = async (envName?: string, appId?: string): Promise<{ entities: Form[] }> => {
193
+ const environmentName = envName || this.#envName;
194
+ const resolvedAppId = appId || this.#appId;
195
+ try {
196
+ let nextToken: string | undefined;
197
+ const uibuilderForms: Form[] = [];
198
+ do {
199
+ const response = await this.#amplifyUiBuilder
200
+ .exportForms({
201
+ appId: resolvedAppId,
202
+ environmentName,
203
+ nextToken,
204
+ })
205
+ .promise();
206
+ uibuilderForms.push(...response.entities);
207
+ nextToken = response.nextToken;
208
+ } while (nextToken);
209
+ printer.debug(JSON.stringify(uibuilderForms, null, 2));
210
+ return { entities: uibuilderForms };
211
+ } catch (err) {
212
+ throw new Error(`Failed to list forms: ${err.message}`);
213
+ }
214
+ };
215
+
216
+ createComponent = async (component: CreateComponentData, envName?: string, appId?: string): Promise<Component | undefined> => {
217
+ const environmentName = envName || this.#envName;
218
+ const resolvedAppId = appId || this.#appId;
219
+ try {
220
+ const response = await this.#amplifyUiBuilder
221
+ .createComponent({
222
+ appId: resolvedAppId,
223
+ environmentName,
224
+ componentToCreate: component,
225
+ })
226
+ .promise();
227
+ return response.entity;
228
+ } catch (err) {
229
+ throw new Error(`Failed to create component: ${err.message}`);
230
+ }
231
+ };
232
+
233
+ getModels = async (resourceName: string, envName?: string, appId?: string): Promise<string | undefined> => {
234
+ try {
235
+ const environmentName = envName || this.#envName;
236
+ const resolvedAppId = appId || this.#appId;
237
+ const { Models } = await this.#amplifyBackend
238
+ .getBackendAPIModels({
239
+ AppId: resolvedAppId,
240
+ BackendEnvironmentName: environmentName,
241
+ ResourceName: resourceName,
242
+ })
243
+ .promise();
244
+ return Models;
245
+ } catch (err) {
246
+ throw new Error(`Models not found in AmplifyBackend:GetBackendAPIModels response: ${err.message}`);
247
+ }
248
+ };
161
249
  }
@@ -1,4 +1,5 @@
1
- import { StudioComponent } from '@aws-amplify/codegen-ui';
1
+ /* eslint-disable spellcheck/spell-checker */
2
+ import { StudioSchema } from '@aws-amplify/codegen-ui';
2
3
  import ora from 'ora';
3
4
  import { printer } from 'amplify-prompts';
4
5
  import { $TSContext } from 'amplify-cli-core';
@@ -10,6 +11,8 @@ import {
10
11
  generateUiBuilderThemes,
11
12
  getAmplifyDataSchema,
12
13
  generateAmplifyUiBuilderIndexFile,
14
+ generateUiBuilderForms,
15
+ generateAmplifyUiBuilderUtilFile,
13
16
  } from './utils';
14
17
 
15
18
  /**
@@ -22,36 +25,60 @@ export const run = async (context: $TSContext): Promise<void> => {
22
25
  const spinner = ora('');
23
26
  try {
24
27
  const studioClient = await AmplifyStudioClient.setClientInfo(context);
25
- const [componentSchemas, themeSchemas, dataSchema] = await Promise.all([
28
+ const [componentSchemas, themeSchemas, formSchemas, dataSchema] = await Promise.all([
26
29
  studioClient.listComponents(),
27
30
  studioClient.listThemes(),
31
+ studioClient.listForms(),
28
32
  getAmplifyDataSchema(studioClient),
29
33
  ]);
30
- if (componentSchemas.entities.length === 0 && themeSchemas.entities.length === 0) {
34
+
35
+ const nothingWouldAutogen = !dataSchema || !studioClient.metadata.autoGenerateForms || !studioClient.isGraphQLSupported;
36
+
37
+ if (nothingWouldAutogen && [componentSchemas, themeSchemas, formSchemas].every(group => !group.entities.length)) {
31
38
  printer.debug('Skipping UI component generation since none are found.');
32
39
  return;
33
40
  }
34
41
  spinner.start('Generating UI components...');
35
- const generatedComponentResults = generateUiBuilderComponents(context, componentSchemas.entities, dataSchema);
36
- const generatedThemeResults = generateUiBuilderThemes(context, themeSchemas.entities);
37
42
 
38
- generateAmplifyUiBuilderIndexFile(context, [
39
- ...generatedComponentResults.filter(({ resultType }) => resultType === 'SUCCESS').map(({ component }) => component),
40
- ...generatedThemeResults.filter(({ resultType }) => resultType === 'SUCCESS').map(({ theme }) => theme),
41
- ] as StudioComponent[]);
43
+ const generatedResults = {
44
+ component: generateUiBuilderComponents(context, componentSchemas.entities, dataSchema),
45
+ theme: generateUiBuilderThemes(context, themeSchemas.entities),
46
+ form: generateUiBuilderForms(
47
+ context, formSchemas.entities, dataSchema, studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported,
48
+ ),
49
+ };
42
50
 
43
- const failedSchemas = [
44
- ...generatedComponentResults.filter(({ resultType }) => resultType === 'FAILURE').map(({ schemaName }) => schemaName),
45
- ...generatedThemeResults.filter(({ resultType }) => resultType === 'FAILURE').map(({ schemaName }) => schemaName),
46
- ];
51
+ const successfulSchemas: StudioSchema[] = [];
52
+ let hasSuccessfulForm = false;
53
+ const failedResponseNames: string[] = [];
54
+
55
+ Object.entries(generatedResults).forEach(([key, results]) => {
56
+ results.forEach(result => {
57
+ if (result.resultType === 'SUCCESS') {
58
+ successfulSchemas.push(result.schema);
59
+ if (key === 'form') {
60
+ hasSuccessfulForm = true;
61
+ }
62
+ } else {
63
+ failedResponseNames.push(result.schemaName);
64
+ }
65
+ });
66
+ });
47
67
 
48
- if (failedSchemas.length > 0) {
49
- spinner.fail(`Failed to sync the following components: ${failedSchemas.join(', ')}`);
68
+ generateAmplifyUiBuilderIndexFile(context, successfulSchemas);
69
+
70
+ generateAmplifyUiBuilderUtilFile(context, { hasForms: hasSuccessfulForm, hasViews: false });
71
+
72
+ if (failedResponseNames.length > 0) {
73
+ spinner.fail(`Failed to sync the following components: ${failedResponseNames.join(', ')}`);
50
74
  } else {
51
75
  spinner.succeed('Synced UI components.');
52
76
  }
53
77
 
54
- const invalidComponentNames = componentSchemas.entities.filter(component => !component.schemaVersion).map(component => component.name);
78
+ const invalidComponentNames = [
79
+ ...componentSchemas.entities.filter(component => !component.schemaVersion).map(component => component.name),
80
+ ...formSchemas.entities.filter(form => !form.schemaVersion).map(form => form.name),
81
+ ];
55
82
  if (invalidComponentNames.length) {
56
83
  printer.warn(
57
84
  `The components ${invalidComponentNames.join(
@@ -1,5 +1,8 @@
1
1
  import { printer } from 'amplify-prompts';
2
2
 
3
- export async function run() {
3
+ /**
4
+ * help command
5
+ */
6
+ export const run = (): void => {
4
7
  printer.info('Supported commands: generateComponents, cloneComponentsFromEnv');
5
- }
8
+ };
@@ -10,14 +10,20 @@ import {
10
10
  RenderTextComponentResponse,
11
11
  GenericDataSchema,
12
12
  getGenericFromDataStore,
13
+ StudioForm,
14
+ StudioSchema,
13
15
  } from '@aws-amplify/codegen-ui';
14
16
  import {
15
17
  AmplifyRenderer,
18
+ AmplifyFormRenderer,
16
19
  ReactThemeStudioTemplateRenderer,
17
20
  ReactIndexStudioTemplateRenderer,
18
21
  ModuleKind,
19
22
  ScriptTarget,
20
23
  ScriptKind,
24
+ UtilTemplateType,
25
+ ReactUtilsStudioTemplateRenderer,
26
+ ReactThemeStudioTemplateRendererOptions,
21
27
  } from '@aws-amplify/codegen-ui-react';
22
28
  import { printer } from 'amplify-prompts';
23
29
  import {
@@ -55,10 +61,14 @@ export const createUiBuilderComponent = (context: $TSContext, schema: StudioComp
55
61
  /**
56
62
  * Writes theme file to the work space
57
63
  */
58
- export const createUiBuilderTheme = (context: $TSContext, schema: StudioTheme): StudioTheme => {
64
+ export const createUiBuilderTheme = (
65
+ context: $TSContext,
66
+ schema: StudioTheme,
67
+ options?: ReactThemeStudioTemplateRendererOptions,
68
+ ): StudioTheme => {
59
69
  const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
60
70
  const rendererFactory = new StudioTemplateRendererFactory(
61
- (component: StudioTheme) => (new ReactThemeStudioTemplateRenderer(component, config) as unknown) as StudioTemplateRenderer<
71
+ (component: StudioTheme) => (new ReactThemeStudioTemplateRenderer(component, config, options) as unknown) as StudioTemplateRenderer<
62
72
  unknown,
63
73
  StudioTheme,
64
74
  FrameworkOutputManager<unknown>,
@@ -83,15 +93,46 @@ export const createUiBuilderTheme = (context: $TSContext, schema: StudioTheme):
83
93
  }
84
94
  };
85
95
 
96
+ /**
97
+ * Writes form file to the work space
98
+ */
99
+ export const createUiBuilderForm = (context: $TSContext, schema: StudioForm, dataSchema?: GenericDataSchema): StudioForm => {
100
+ const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
101
+ const rendererFactory = new StudioTemplateRendererFactory(
102
+ (form: StudioForm) => (new AmplifyFormRenderer(form, dataSchema, config) as unknown) as StudioTemplateRenderer<
103
+ unknown,
104
+ StudioForm,
105
+ FrameworkOutputManager<unknown>,
106
+ RenderTextComponentResponse
107
+ >,
108
+ );
109
+
110
+ const outputPathDir = uiBuilderComponentsPath;
111
+
112
+ const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
113
+ outputPathDir,
114
+ });
115
+
116
+ try {
117
+ rendererManager.renderSchemaToTemplate(schema);
118
+ return schema;
119
+ } catch (e) {
120
+ printer.debug(e);
121
+ printer.debug('Skipping invalid form with schema');
122
+ printer.debug(JSON.stringify(schema, null, 2));
123
+ throw e;
124
+ }
125
+ };
126
+
86
127
  /**
87
128
  * Writes index file to the work space
88
129
  */
89
- export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas: StudioComponent[]): RenderTextComponentResponse => {
130
+ export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas: StudioSchema[]): void => {
90
131
  const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
91
132
  const rendererFactory = new StudioTemplateRendererFactory(
92
- (component: StudioComponent[]) => (new ReactIndexStudioTemplateRenderer(component, config) as unknown) as StudioTemplateRenderer<
133
+ (schema: StudioSchema[]) => (new ReactIndexStudioTemplateRenderer(schema, config) as unknown) as StudioTemplateRenderer<
93
134
  unknown,
94
- StudioComponent[],
135
+ StudioSchema[],
95
136
  FrameworkOutputManager<unknown>,
96
137
  RenderTextComponentResponse
97
138
  >,
@@ -104,7 +145,50 @@ export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas:
104
145
  });
105
146
 
106
147
  try {
107
- return rendererManager.renderSchemaToTemplate(schemas);
148
+ rendererManager.renderSchemaToTemplate(schemas);
149
+ } catch (e) {
150
+ printer.debug(e);
151
+ printer.debug('Failed to generate component index file');
152
+ throw e;
153
+ }
154
+ };
155
+
156
+ type UtilFileChecks = {
157
+ hasForms: boolean;
158
+ hasViews: boolean;
159
+ };
160
+
161
+ /**
162
+ * Writes utils file to the work space
163
+ */
164
+ export const generateAmplifyUiBuilderUtilFile = (context: $TSContext, { hasForms, hasViews }: UtilFileChecks): void => {
165
+ const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
166
+ const rendererFactory = new StudioTemplateRendererFactory(
167
+ (utils: UtilTemplateType[]) => (new ReactUtilsStudioTemplateRenderer(utils, config)),
168
+ );
169
+
170
+ const outputPathDir = uiBuilderComponentsPath;
171
+
172
+ const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
173
+ outputPathDir,
174
+ });
175
+
176
+ const utils = new Set<UtilTemplateType>();
177
+
178
+ if (hasForms) {
179
+ utils.add('validation');
180
+ utils.add('formatter');
181
+ utils.add('fetchByPath');
182
+ }
183
+
184
+ if (hasViews) {
185
+ utils.add('formatter');
186
+ }
187
+
188
+ try {
189
+ if (utils.size) {
190
+ rendererManager.renderSchemaToTemplate([...utils]);
191
+ }
108
192
  } catch (e) {
109
193
  printer.debug(e);
110
194
  printer.debug('Failed to generate component index file');
@@ -119,6 +203,9 @@ export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas:
119
203
  export const getAmplifyDataSchema = async (
120
204
  studioClient: AmplifyStudioClient,
121
205
  ): Promise<GenericDataSchema | undefined> => {
206
+ if (!studioClient.isGraphQLSupported) {
207
+ return undefined;
208
+ }
122
209
  try {
123
210
  const meta = stateManager.getMeta();
124
211
  const resourceName = Object.entries(meta[AmplifyCategories.API]).find(
@@ -138,3 +225,25 @@ export const getAmplifyDataSchema = async (
138
225
  return undefined;
139
226
  }
140
227
  };
228
+
229
+ /**
230
+ * generates base create/update froms from names
231
+ */
232
+ export const generateBaseForms = (modelMap: {[model: string]: Set<'create' | 'update'>}): StudioForm[] => {
233
+ const getSchema = (name: string, type: 'create' | 'update') : StudioForm => ({
234
+ name: `${name}${type === 'create' ? 'CreateForm' : 'UpdateForm'}`,
235
+ formActionType: type,
236
+ dataType: { dataSourceType: 'DataStore', dataTypeName: name },
237
+ fields: {},
238
+ sectionalElements: {},
239
+ style: {},
240
+ cta: {},
241
+ });
242
+
243
+ const schemas : StudioForm[] = [];
244
+
245
+ Object.entries(modelMap).forEach(([name, set]) => {
246
+ set.forEach(type => schemas.push(getSchema(name, type)));
247
+ });
248
+ return schemas;
249
+ };
@@ -0,0 +1,62 @@
1
+ /* eslint-disable spellcheck/spell-checker */
2
+ import {
3
+ FeatureFlags, pathManager, stateManager,
4
+ } from 'amplify-cli-core';
5
+ import { printer } from 'amplify-prompts';
6
+
7
+ // This is copy-pasta from the category api until they can expose the method to plugins
8
+
9
+ /**
10
+ * Shorthand for Feature flag retrieval.
11
+ */
12
+ const useExperimentalPipelinedTransformerFF = (): boolean => FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer');
13
+ const transformerVersionFF = (): number => FeatureFlags.getNumber('graphQLTransformer.transformerVersion');
14
+
15
+ /**
16
+ * Inspect feature flags for the project and determine if this particular project should be using graphql v2 or v1.
17
+ * Coerces feature flags into a sane state if they are out of, and throws error on invalid configuration.
18
+ */
19
+ export const getTransformerVersion = async (): Promise<number> => {
20
+ if (useExperimentalPipelinedTransformerFF() === false) {
21
+ return 1;
22
+ }
23
+
24
+ if (isLegacyFeatureFlagConfiguration()) {
25
+ await migrateToTransformerVersionFeatureFlag();
26
+ }
27
+
28
+ const transformerVersion = transformerVersionFF();
29
+ if (transformerVersion !== 1 && transformerVersion !== 2) {
30
+ throw new Error(`Invalid value specified for transformerVersion: '${transformerVersion}'`);
31
+ }
32
+
33
+ return transformerVersion;
34
+ };
35
+
36
+ /**
37
+ * Return whether or not the project is configured with legacy pipelined transformer feature flags, and may need to be updated.
38
+ */
39
+ const isLegacyFeatureFlagConfiguration = (): boolean => useExperimentalPipelinedTransformerFF() && transformerVersionFF() === 1;
40
+
41
+ /**
42
+ * Update project feature flags and alert the user if they have a deprecated set of feature flags.
43
+ */
44
+ const migrateToTransformerVersionFeatureFlag = async (): Promise<void> => {
45
+ const projectPath = pathManager.findProjectRoot() ?? process.cwd();
46
+
47
+ const config = stateManager.getCLIJSON(projectPath, undefined, {
48
+ throwIfNotExist: false,
49
+ preserveComments: true,
50
+ });
51
+
52
+ // eslint-disable-next-line spellcheck/spell-checker
53
+ config.features.graphqltransformer.transformerversion = 2;
54
+ stateManager.setCLIJSON(projectPath, config);
55
+ await FeatureFlags.reloadValues();
56
+ // eslint-disable-next-line spellcheck/spell-checker
57
+ printer.warn(
58
+ `\nThe project is configured with 'transformerVersion': ${transformerVersionFF()}, but 'useExperimentalPipelinedTransformer': ${useExperimentalPipelinedTransformerFF()}. Setting the 'transformerVersion': ${
59
+ config.features.graphqltransformer.transformerversion
60
+ }. 'useExperimentalPipelinedTransformer' is deprecated.`,
61
+ );
62
+ };
@@ -1,14 +1,19 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs-extra';
3
- import { extractArgs } from './extractArgs';
4
3
  import { $TSContext } from 'amplify-cli-core';
4
+ import { extractArgs } from './extractArgs';
5
5
 
6
- export const getUiBuilderComponentsPath = (context: $TSContext) => {
6
+ /**
7
+ *
8
+ * locates components path directory
9
+ */
10
+ export const getUiBuilderComponentsPath = (context: $TSContext): string => {
7
11
  const args = extractArgs(context);
8
12
  const srcDir = args.srcDir ? args.srcDir : context.exeInfo.projectConfig.javascript.config.SourceDir;
9
13
  const uiBuilderComponentsPath = path.resolve(path.join('.', srcDir, 'ui-components'));
10
14
 
11
15
  if (!fs.existsSync(uiBuilderComponentsPath)) {
16
+ // eslint-disable-next-line spellcheck/spell-checker
12
17
  fs.mkdirpSync(uiBuilderComponentsPath);
13
18
  }
14
19