@aws-amplify/amplify-util-uibuilder 1.6.2 → 1.6.3-graphql15.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.
Files changed (66) hide show
  1. package/API.md +19 -0
  2. package/CHANGELOG.md +13 -0
  3. package/amplify-plugin.json +3 -1
  4. package/lib/clients/amplify-studio-client.d.ts +1 -0
  5. package/lib/clients/amplify-studio-client.d.ts.map +1 -1
  6. package/lib/clients/amplify-studio-client.js +13 -0
  7. package/lib/clients/amplify-studio-client.js.map +1 -1
  8. package/lib/commands/cloneComponentsFromEnv.js +2 -2
  9. package/lib/commands/cloneComponentsFromEnv.js.map +1 -1
  10. package/lib/commands/generateComponents.d.ts +1 -1
  11. package/lib/commands/generateComponents.d.ts.map +1 -1
  12. package/lib/commands/generateComponents.js +15 -3
  13. package/lib/commands/generateComponents.js.map +1 -1
  14. package/lib/commands/utils/codegenResources.d.ts.map +1 -1
  15. package/lib/commands/utils/codegenResources.js +3 -1
  16. package/lib/commands/utils/codegenResources.js.map +1 -1
  17. package/lib/commands/utils/detachedFormCleanup.d.ts +11 -0
  18. package/lib/commands/utils/detachedFormCleanup.d.ts.map +1 -0
  19. package/lib/commands/utils/detachedFormCleanup.js +47 -0
  20. package/lib/commands/utils/detachedFormCleanup.js.map +1 -0
  21. package/lib/commands/utils/index.d.ts +1 -0
  22. package/lib/commands/utils/index.d.ts.map +1 -1
  23. package/lib/commands/utils/index.js +1 -0
  24. package/lib/commands/utils/index.js.map +1 -1
  25. package/lib/commands/utils/notifyMissingPackages.js +2 -2
  26. package/lib/commands/utils/notifyMissingPackages.js.map +1 -1
  27. package/lib/commands/utils/shouldRenderComponents.d.ts.map +1 -1
  28. package/lib/commands/utils/shouldRenderComponents.js +7 -5
  29. package/lib/commands/utils/shouldRenderComponents.js.map +1 -1
  30. package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts.map +1 -1
  31. package/lib/commands/utils/syncAmplifyUiBuilderComponents.js +3 -1
  32. package/lib/commands/utils/syncAmplifyUiBuilderComponents.js.map +1 -1
  33. package/lib/event-handlers/handle-PostPull.js +1 -1
  34. package/lib/event-handlers/handle-PostPull.js.map +1 -1
  35. package/lib/event-handlers/handle-PostPush.js +1 -1
  36. package/lib/event-handlers/handle-PostPush.js.map +1 -1
  37. package/lib/event-handlers/handle-PrePush.d.ts +3 -0
  38. package/lib/event-handlers/handle-PrePush.d.ts.map +1 -0
  39. package/lib/event-handlers/handle-PrePush.js +15 -0
  40. package/lib/event-handlers/handle-PrePush.js.map +1 -0
  41. package/lib/utils/prePushHandler.d.ts +3 -0
  42. package/lib/utils/prePushHandler.d.ts.map +1 -0
  43. package/lib/utils/prePushHandler.js +40 -0
  44. package/lib/utils/prePushHandler.js.map +1 -0
  45. package/package.json +12 -9
  46. package/src/__tests__/createUiBuilderComponent.test.ts +4 -0
  47. package/src/__tests__/detachedFormCleanup.test.ts +78 -0
  48. package/src/__tests__/generateComponents.test.ts +13 -11
  49. package/src/__tests__/handleEvents.test.ts +27 -0
  50. package/src/__tests__/prePushHandler.test.ts +105 -0
  51. package/src/__tests__/shouldRenderComponents.test.ts +48 -35
  52. package/src/__tests__/syncAmplifyUiComponents.test.ts +3 -3
  53. package/src/clients/amplify-studio-client.ts +13 -0
  54. package/src/commands/cloneComponentsFromEnv.ts +1 -1
  55. package/src/commands/generateComponents.ts +30 -4
  56. package/src/commands/utils/codegenResources.ts +3 -1
  57. package/src/commands/utils/detachedFormCleanup.ts +58 -0
  58. package/src/commands/utils/index.ts +1 -0
  59. package/src/commands/utils/notifyMissingPackages.ts +2 -2
  60. package/src/commands/utils/shouldRenderComponents.ts +7 -5
  61. package/src/commands/utils/syncAmplifyUiBuilderComponents.ts +4 -1
  62. package/src/event-handlers/handle-PostPull.ts +1 -1
  63. package/src/event-handlers/handle-PostPush.ts +1 -1
  64. package/src/event-handlers/handle-PrePush.ts +15 -0
  65. package/src/utils/prePushHandler.ts +55 -0
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,58 @@
1
+ import { StudioForm, StudioSchema } from '@aws-amplify/codegen-ui';
2
+ import { Form } from 'aws-sdk/clients/amplifyuibuilder';
3
+ import { AmplifyStudioClient } from '../../clients';
4
+ import { printer } from 'amplify-prompts';
5
+ import asyncPool from 'tiny-async-pool';
6
+
7
+ /**
8
+ * Does the form's reference DataStore type exist in the list of models
9
+ */
10
+ export const isFormDetachedFromModel = (schema: StudioForm | Form, models: Set<string>): boolean => {
11
+ if (schema.dataType.dataSourceType === 'DataStore' && !models.has(schema.dataType.dataTypeName)) {
12
+ return true;
13
+ }
14
+ return false;
15
+ };
16
+
17
+ /**
18
+ * A form schema will be customized if fields, style, sectionalElements objects aren't empty
19
+ * @param schema The schema to test
20
+ * @returns boolean
21
+ */
22
+ export const isFormSchemaCustomized = (schema: StudioForm | Form): boolean => {
23
+ const { fields, style, sectionalElements } = schema;
24
+ // Fields and sectionalElements just need an empty check
25
+ if (!isEmpty({ ...fields, ...sectionalElements })) {
26
+ return true;
27
+ }
28
+
29
+ // Check style
30
+ // If not empty, at least one style must be defined
31
+ return Object.values(style).some(styleConfig => styleConfig !== undefined);
32
+ };
33
+
34
+ /**
35
+ * Is the schema a StudioForm
36
+ */
37
+ export const isStudioForm = (schema: StudioSchema | undefined): schema is StudioForm => {
38
+ if (!schema) return false;
39
+ return 'formActionType' in schema;
40
+ };
41
+
42
+ export const deleteDetachedForms = async (detachedForms: {id: string, name: string}[], studioClient: AmplifyStudioClient) => {
43
+ const deleteForm = async ({id, name}: {id: string, name: string}) => {
44
+ try {
45
+ // Try to delete the form but don't stop any other operations if it fails
46
+ await studioClient.deleteForm(id);
47
+ return {status: 'SUCCESS', message: `Deleted detached form ${name}`};
48
+ } catch (error) {
49
+ return {status: 'FAIL', message: `Failed to delete detached form ${name}`}
50
+ }
51
+ };
52
+ for await (const status of asyncPool(5, detachedForms, deleteForm)) {
53
+ printer.debug(status.message);
54
+ };
55
+ };
56
+
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ const isEmpty = (obj?: any): boolean => Object.keys(obj).length === 0;
@@ -5,3 +5,4 @@ export * from './getUiBuilderComponentsPath';
5
5
  export * from './notifyMissingPackages';
6
6
  export * from './shouldRenderComponents';
7
7
  export * from './syncAmplifyUiBuilderComponents';
8
+ export * from './detachedFormCleanup';
@@ -33,11 +33,11 @@ export const notifyMissingPackages = (context: $TSContext): void => {
33
33
  const packageIsInstalled = Object.keys(packageJson.dependencies).includes(dependency.dependencyName);
34
34
  if (!packageIsInstalled) {
35
35
  printer.warn(
36
- `UIBuilder components required "${dependency.dependencyName}" that is not in your package.json. Run \`npm install "${dependency.dependencyName}@${dependency.supportedSemVerPattern}"\`. ${dependency.reason}`,
36
+ `UIBuilder components require "${dependency.dependencyName}" that is not in your package.json. Run \`npm install "${dependency.dependencyName}@${dependency.supportedSemVerPattern}"\`. ${dependency.reason}`,
37
37
  );
38
38
  } else if (!rangeSubset(packageJson.dependencies[dependency.dependencyName], dependency.supportedSemVerPattern)) {
39
39
  printer.warn(
40
- `UIBuilder components requires version "${dependency.supportedSemVerPattern}" of "${
40
+ `UIBuilder components require version "${dependency.supportedSemVerPattern}" of "${
41
41
  dependency.dependencyName
42
42
  }". You currently are on version "${packageJson.dependencies[dependency.dependencyName]}". Run \`npm install "${
43
43
  dependency.dependencyName
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
- import { $TSContext } from 'amplify-cli-core';
2
+ import { $TSContext, stateManager } from 'amplify-cli-core';
3
3
  import { printer } from 'amplify-prompts';
4
4
  import { AmplifyStudioClient } from '../../clients';
5
5
  import { getAppId } from './environmentHelpers';
@@ -8,6 +8,8 @@ import { getAppId } from './environmentHelpers';
8
8
  * process to decide if we should render components
9
9
  */
10
10
  export const shouldRenderComponents = async (context: $TSContext): Promise<boolean> => {
11
+ const projectConfig = stateManager.getProjectConfig();
12
+
11
13
  if (process.env.FORCE_RENDER) {
12
14
  printer.debug('Forcing component render since environment variable flag is set.');
13
15
  return true;
@@ -17,22 +19,22 @@ export const shouldRenderComponents = async (context: $TSContext): Promise<boole
17
19
  return false;
18
20
  }
19
21
 
20
- if (!context?.exeInfo?.projectConfig) {
22
+ if (!projectConfig) {
21
23
  printer.debug('Not pulling components because there is no projectConfig set for this project.');
22
24
  return false;
23
25
  }
24
26
 
25
- if (!context.exeInfo.projectConfig.providers.includes('awscloudformation')) {
27
+ if (!projectConfig.providers.includes('awscloudformation')) {
26
28
  printer.debug('Not pulling components because there is no "awscloudformation" provider.');
27
29
  return false;
28
30
  }
29
31
 
30
- if (context.exeInfo.projectConfig.frontend !== 'javascript') {
32
+ if (projectConfig.frontend !== 'javascript') {
31
33
  printer.debug('Not pulling components because this project is not configured as a javascript frontend.');
32
34
  return false;
33
35
  }
34
36
 
35
- if (context.exeInfo.projectConfig.javascript.framework !== 'react') {
37
+ if (projectConfig.javascript.framework !== 'react') {
36
38
  printer.debug('Not pulling components because this project is not configured with the "react" framework.');
37
39
  return false;
38
40
  }
@@ -1,3 +1,4 @@
1
+ // es-lint-disable-file spellcheck/spell-checker
1
2
  import { printer } from 'amplify-prompts';
2
3
  import { $TSContext } from 'amplify-cli-core';
3
4
  import {
@@ -120,7 +121,9 @@ export const generateUiBuilderForms = (
120
121
  } catch (e) {
121
122
  printer.debug(`Failure caught processing ${schema.name}`);
122
123
  printer.debug(e);
123
- return { resultType: 'FAILURE', schemaName: schema.name, error: e };
124
+ return {
125
+ resultType: 'FAILURE', schemaName: schema.name, schema, error: e,
126
+ };
124
127
  }
125
128
  };
126
129
  const formResults = formSchemas.map((schema: StudioForm) => {
@@ -7,7 +7,7 @@ import { run as generateComponents } from '../commands/generateComponents';
7
7
  */
8
8
  export const run = async (context: $TSContext): Promise<void> => {
9
9
  try {
10
- await generateComponents(context);
10
+ await generateComponents(context, 'PostPull');
11
11
  } catch (e) {
12
12
  // Swallow all errors from the uibuilder plugin
13
13
  printer.debug(e);
@@ -7,7 +7,7 @@ import { run as generateComponents } from '../commands/generateComponents';
7
7
  */
8
8
  export const run = async (context: $TSContext): Promise<void> => {
9
9
  try {
10
- await generateComponents(context);
10
+ await generateComponents(context, 'PostPush');
11
11
  } catch (e) {
12
12
  // Swallow all errors from the uibuilder plugin
13
13
  printer.debug(e);
@@ -0,0 +1,15 @@
1
+ import { $TSContext } from 'amplify-cli-core';
2
+ import { printer } from 'amplify-prompts';
3
+ import { prePushHandler } from '../utils/prePushHandler';
4
+
5
+ /**
6
+ * The code to run before a push.
7
+ */
8
+ export const run = async (context: $TSContext): Promise<void> => {
9
+ try {
10
+ await prePushHandler(context);
11
+ } catch (e) {
12
+ // Swallow all errors from the uibuilder plugin
13
+ printer.debug(e);
14
+ }
15
+ };
@@ -0,0 +1,55 @@
1
+ import { $TSContext } from 'amplify-cli-core';
2
+ import type { Schema } from '@aws-amplify/datastore';
3
+ import { printer } from 'amplify-prompts';
4
+ import AmplifyUIBuilder from 'aws-sdk/clients/amplifyuibuilder';
5
+ import { AmplifyStudioClient } from '../clients';
6
+ import { isFormDetachedFromModel, isFormSchemaCustomized, shouldRenderComponents } from '../commands/utils';
7
+
8
+ /**
9
+ * Handles showing a warning for detached forms pre-push.
10
+ */
11
+ export const prePushHandler = async (context: $TSContext): Promise<void> => {
12
+ if (!(await shouldRenderComponents(context))) {
13
+ return;
14
+ }
15
+ try {
16
+ const studioClient = await AmplifyStudioClient.setClientInfo(context);
17
+ const [formSchemas, localSchema] = await Promise.all<[Promise<{
18
+ entities: AmplifyUIBuilder.Form[];
19
+ }>, Promise<Schema>]>([
20
+ studioClient.listForms(),
21
+ context.amplify.invokePluginMethod(
22
+ context,
23
+ 'codegen',
24
+ undefined,
25
+ 'getModelIntrospection',
26
+ [context],
27
+ ),
28
+ ]);
29
+
30
+ if (!localSchema) {
31
+ printer.debug('Local schema not found');
32
+ return;
33
+ }
34
+ printDetachedFormsWarning(formSchemas, localSchema);
35
+ } catch (error) {
36
+ printer.debug(error);
37
+ }
38
+ };
39
+
40
+ const printDetachedFormsWarning = (formSchemas: { entities: AmplifyUIBuilder.Form[] }, localSchema: Schema): void => {
41
+ const modelNames = new Set(Object.keys(localSchema.models));
42
+ const detachedCustomForms: string[] = [];
43
+
44
+ formSchemas.entities.forEach(form => {
45
+ // Only show warnings for schema's that have customization, otherwise it's the same as
46
+ // an autogenerated form.
47
+ if (isFormDetachedFromModel(form, modelNames) && isFormSchemaCustomized(form)) {
48
+ detachedCustomForms.push(form.name);
49
+ }
50
+ });
51
+
52
+ if (detachedCustomForms.length > 0) {
53
+ printer.warn(`The following form${detachedCustomForms.length === 1 ? '' : 's'} will no longer be available because the connected data model no longer exists: ${detachedCustomForms.join(', ')}`);
54
+ }
55
+ };