@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.
- package/API.md +19 -0
- package/CHANGELOG.md +13 -0
- package/amplify-plugin.json +3 -1
- 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 +13 -0
- package/lib/clients/amplify-studio-client.js.map +1 -1
- package/lib/commands/cloneComponentsFromEnv.js +2 -2
- package/lib/commands/cloneComponentsFromEnv.js.map +1 -1
- package/lib/commands/generateComponents.d.ts +1 -1
- package/lib/commands/generateComponents.d.ts.map +1 -1
- package/lib/commands/generateComponents.js +15 -3
- package/lib/commands/generateComponents.js.map +1 -1
- package/lib/commands/utils/codegenResources.d.ts.map +1 -1
- package/lib/commands/utils/codegenResources.js +3 -1
- package/lib/commands/utils/codegenResources.js.map +1 -1
- package/lib/commands/utils/detachedFormCleanup.d.ts +11 -0
- package/lib/commands/utils/detachedFormCleanup.d.ts.map +1 -0
- package/lib/commands/utils/detachedFormCleanup.js +47 -0
- package/lib/commands/utils/detachedFormCleanup.js.map +1 -0
- package/lib/commands/utils/index.d.ts +1 -0
- package/lib/commands/utils/index.d.ts.map +1 -1
- package/lib/commands/utils/index.js +1 -0
- package/lib/commands/utils/index.js.map +1 -1
- package/lib/commands/utils/notifyMissingPackages.js +2 -2
- package/lib/commands/utils/notifyMissingPackages.js.map +1 -1
- package/lib/commands/utils/shouldRenderComponents.d.ts.map +1 -1
- package/lib/commands/utils/shouldRenderComponents.js +7 -5
- package/lib/commands/utils/shouldRenderComponents.js.map +1 -1
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.d.ts.map +1 -1
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.js +3 -1
- package/lib/commands/utils/syncAmplifyUiBuilderComponents.js.map +1 -1
- package/lib/event-handlers/handle-PostPull.js +1 -1
- package/lib/event-handlers/handle-PostPull.js.map +1 -1
- package/lib/event-handlers/handle-PostPush.js +1 -1
- package/lib/event-handlers/handle-PostPush.js.map +1 -1
- package/lib/event-handlers/handle-PrePush.d.ts +3 -0
- package/lib/event-handlers/handle-PrePush.d.ts.map +1 -0
- package/lib/event-handlers/handle-PrePush.js +15 -0
- package/lib/event-handlers/handle-PrePush.js.map +1 -0
- package/lib/utils/prePushHandler.d.ts +3 -0
- package/lib/utils/prePushHandler.d.ts.map +1 -0
- package/lib/utils/prePushHandler.js +40 -0
- package/lib/utils/prePushHandler.js.map +1 -0
- package/package.json +12 -9
- package/src/__tests__/createUiBuilderComponent.test.ts +4 -0
- package/src/__tests__/detachedFormCleanup.test.ts +78 -0
- package/src/__tests__/generateComponents.test.ts +13 -11
- package/src/__tests__/handleEvents.test.ts +27 -0
- package/src/__tests__/prePushHandler.test.ts +105 -0
- package/src/__tests__/shouldRenderComponents.test.ts +48 -35
- package/src/__tests__/syncAmplifyUiComponents.test.ts +3 -3
- package/src/clients/amplify-studio-client.ts +13 -0
- package/src/commands/cloneComponentsFromEnv.ts +1 -1
- package/src/commands/generateComponents.ts +30 -4
- package/src/commands/utils/codegenResources.ts +3 -1
- package/src/commands/utils/detachedFormCleanup.ts +58 -0
- package/src/commands/utils/index.ts +1 -0
- package/src/commands/utils/notifyMissingPackages.ts +2 -2
- package/src/commands/utils/shouldRenderComponents.ts +7 -5
- package/src/commands/utils/syncAmplifyUiBuilderComponents.ts +4 -1
- package/src/event-handlers/handle-PostPull.ts +1 -1
- package/src/event-handlers/handle-PostPush.ts +1 -1
- package/src/event-handlers/handle-PrePush.ts +15 -0
- package/src/utils/prePushHandler.ts +55 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prePushHandler = void 0;
|
|
4
|
+
const amplify_prompts_1 = require("amplify-prompts");
|
|
5
|
+
const clients_1 = require("../clients");
|
|
6
|
+
const utils_1 = require("../commands/utils");
|
|
7
|
+
const prePushHandler = async (context) => {
|
|
8
|
+
if (!(await (0, utils_1.shouldRenderComponents)(context))) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const studioClient = await clients_1.AmplifyStudioClient.setClientInfo(context);
|
|
13
|
+
const [formSchemas, localSchema] = await Promise.all([
|
|
14
|
+
studioClient.listForms(),
|
|
15
|
+
context.amplify.invokePluginMethod(context, 'codegen', undefined, 'getModelIntrospection', [context]),
|
|
16
|
+
]);
|
|
17
|
+
if (!localSchema) {
|
|
18
|
+
amplify_prompts_1.printer.debug('Local schema not found');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
printDetachedFormsWarning(formSchemas, localSchema);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
amplify_prompts_1.printer.debug(error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.prePushHandler = prePushHandler;
|
|
28
|
+
const printDetachedFormsWarning = (formSchemas, localSchema) => {
|
|
29
|
+
const modelNames = new Set(Object.keys(localSchema.models));
|
|
30
|
+
const detachedCustomForms = [];
|
|
31
|
+
formSchemas.entities.forEach(form => {
|
|
32
|
+
if ((0, utils_1.isFormDetachedFromModel)(form, modelNames) && (0, utils_1.isFormSchemaCustomized)(form)) {
|
|
33
|
+
detachedCustomForms.push(form.name);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (detachedCustomForms.length > 0) {
|
|
37
|
+
amplify_prompts_1.printer.warn(`The following form${detachedCustomForms.length === 1 ? '' : 's'} will no longer be available because the connected data model no longer exists: ${detachedCustomForms.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=prePushHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prePushHandler.js","sourceRoot":"","sources":["../../src/utils/prePushHandler.ts"],"names":[],"mappings":";;;AAEA,qDAA0C;AAE1C,wCAAiD;AACjD,6CAA4G;AAKrG,MAAM,cAAc,GAAG,KAAK,EAAE,OAAmB,EAAiB,EAAE;IACzE,IAAI,CAAC,CAAC,MAAM,IAAA,8BAAsB,EAAC,OAAO,CAAC,CAAC,EAAE;QAC5C,OAAO;KACR;IACD,IAAI;QACF,MAAM,YAAY,GAAG,MAAM,6BAAmB,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACtE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAE9B;YACpB,YAAY,CAAC,SAAS,EAAE;YACxB,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAChC,OAAO,EACP,SAAS,EACT,SAAS,EACT,uBAAuB,EACvB,CAAC,OAAO,CAAC,CACV;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,WAAW,EAAE;YAChB,yBAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;YACxC,OAAO;SACR;QACD,yBAAyB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;KACrD;IAAC,OAAO,KAAK,EAAE;QACd,yBAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;KACtB;AACH,CAAC,CAAC;AA3BW,QAAA,cAAc,kBA2BzB;AAEF,MAAM,yBAAyB,GAAG,CAAC,WAAkD,EAAE,WAAmB,EAAQ,EAAE;IAClH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,MAAM,mBAAmB,GAAa,EAAE,CAAC;IAEzC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAGlC,IAAI,IAAA,+BAAuB,EAAC,IAAI,EAAE,UAAU,CAAC,IAAI,IAAA,8BAAsB,EAAC,IAAI,CAAC,EAAE;YAC7E,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACrC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE;QAClC,yBAAO,CAAC,IAAI,CAAC,qBAAqB,mBAAmB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,mFAAmF,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;KACnM;AACH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-amplify/amplify-util-uibuilder",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3-graphql15.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc",
|
|
8
|
-
"test": "jest --logHeapUsage"
|
|
8
|
+
"test": "jest --logHeapUsage",
|
|
9
|
+
"extract-api": "ts-node ../../scripts/extract-api.ts"
|
|
9
10
|
},
|
|
10
11
|
"author": "Amazon Web Services",
|
|
11
12
|
"license": "Apache-2.0",
|
|
@@ -13,18 +14,20 @@
|
|
|
13
14
|
"access": "public"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@aws-amplify/codegen-ui": "2.5.
|
|
17
|
-
"@aws-amplify/codegen-ui-react": "2.5.
|
|
18
|
-
"amplify-cli-core": "3.
|
|
19
|
-
"amplify-prompts": "2.6.
|
|
17
|
+
"@aws-amplify/codegen-ui": "2.5.6",
|
|
18
|
+
"@aws-amplify/codegen-ui-react": "2.5.6",
|
|
19
|
+
"amplify-cli-core": "3.5.0-graphql15.0",
|
|
20
|
+
"amplify-prompts": "2.6.2-graphql15.0",
|
|
20
21
|
"aws-sdk": "^2.1233.0",
|
|
21
22
|
"fs-extra": "^8.1.0",
|
|
22
|
-
"ora": "^4.0.3"
|
|
23
|
+
"ora": "^4.0.3",
|
|
24
|
+
"tiny-async-pool": "^2.1.0"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@types/fs-extra": "^8.0.1",
|
|
26
28
|
"@types/jest": "^26.0.20",
|
|
27
|
-
"@types/semver": "^7.1.0"
|
|
29
|
+
"@types/semver": "^7.1.0",
|
|
30
|
+
"@types/tiny-async-pool": "^2.0.0"
|
|
28
31
|
},
|
|
29
32
|
"jest": {
|
|
30
33
|
"testRunner": "jest-circus/runner",
|
|
@@ -68,5 +71,5 @@
|
|
|
68
71
|
"usePathForSuiteName": "true",
|
|
69
72
|
"addFileAttribute": "true"
|
|
70
73
|
},
|
|
71
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "a870ec62056ad90d3fdd4e85749158d981e8a7af"
|
|
72
75
|
}
|
|
@@ -69,6 +69,10 @@ describe('can create a ui builder component', () => {
|
|
|
69
69
|
generateAmplifyUiBuilderIndexFile(context, [schema]);
|
|
70
70
|
expect(new codegenMock.StudioTemplateRendererManager().renderSchemaToTemplate).toBeCalled();
|
|
71
71
|
});
|
|
72
|
+
it('does not call renderSchemaToTemplate for index file if no schema', () => {
|
|
73
|
+
generateAmplifyUiBuilderIndexFile(context, []);
|
|
74
|
+
expect(new codegenMock.StudioTemplateRendererManager().renderSchemaToTemplate).not.toBeCalled();
|
|
75
|
+
})
|
|
72
76
|
it('calls the renderManager for utils file w/ validation, formatter, and fetchByPath helpers if there is a form', () => {
|
|
73
77
|
generateAmplifyUiBuilderUtilFile(context, { hasForms: true, hasViews: false });
|
|
74
78
|
expect(new codegenMock.StudioTemplateRendererManager().renderSchemaToTemplate).toBeCalledWith(expect.arrayContaining(['validation', 'formatter', 'fetchByPath']));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @aws-amplify/codegen-ui is in project dependencies
|
|
2
|
+
import { StudioSchema } from '@aws-amplify/codegen-ui'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import {AmplifyStudioClient} from '../clients'; // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
+
import {
|
|
5
|
+
isFormDetachedFromModel, isFormSchemaCustomized, isStudioForm, deleteDetachedForms
|
|
6
|
+
} from '../commands/utils';
|
|
7
|
+
|
|
8
|
+
const amplifyStudioClientMock = AmplifyStudioClient as any;
|
|
9
|
+
|
|
10
|
+
const formWithNoCustomization: StudioSchema = {
|
|
11
|
+
id: 'f-123456',
|
|
12
|
+
name: 'BlogCreateForm',
|
|
13
|
+
formActionType: 'create',
|
|
14
|
+
dataType: {
|
|
15
|
+
dataSourceType: 'DataStore',
|
|
16
|
+
dataTypeName: 'Blog',
|
|
17
|
+
},
|
|
18
|
+
fields: {},
|
|
19
|
+
sectionalElements: {},
|
|
20
|
+
style: {},
|
|
21
|
+
cta: {},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const formWithStyle: StudioSchema = {
|
|
25
|
+
...formWithNoCustomization,
|
|
26
|
+
dataType: { dataSourceType: 'Custom', dataTypeName: 'Blog' },
|
|
27
|
+
style: {
|
|
28
|
+
horizontalGap: { value: '20px' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const formWithFields: StudioSchema = {
|
|
33
|
+
...formWithNoCustomization,
|
|
34
|
+
dataType: { dataSourceType: 'Custom', dataTypeName: 'Blog' },
|
|
35
|
+
fields: {
|
|
36
|
+
name: { inputType: { type: 'TextField' } },
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe('detachedFormCleanup', () => {
|
|
41
|
+
|
|
42
|
+
it('isStudioForm should return true', () => {
|
|
43
|
+
expect(isStudioForm(formWithNoCustomization)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('isStudioForm should return false', () => {
|
|
47
|
+
expect(isStudioForm(undefined)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('isFormSchemaCustomized should return false', () => {
|
|
51
|
+
expect(isFormSchemaCustomized(formWithNoCustomization)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('isFormSchemaCustomized should return true', () => {
|
|
55
|
+
expect(isFormSchemaCustomized(formWithStyle)).toBe(true);
|
|
56
|
+
expect(isFormSchemaCustomized(formWithFields)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('isFormDetachedFromModel should return true for a DataStore form with detached model', () => {
|
|
60
|
+
expect(isFormDetachedFromModel(formWithNoCustomization, new Set<string>())).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('isFormDetachedFromModel should return false for a Custom form', () => {
|
|
64
|
+
expect(isFormDetachedFromModel(formWithStyle, new Set<string>())).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('isFormDetachedFromModel should return false for a DataStore form with an attached model', () => {
|
|
68
|
+
expect(isFormDetachedFromModel(formWithNoCustomization, new Set<string>().add('Blog'))).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should delete detached forms', async () => {
|
|
72
|
+
const mockedDeleteForm = jest.fn().mockReturnValue(undefined);
|
|
73
|
+
amplifyStudioClientMock.deleteForm = mockedDeleteForm;
|
|
74
|
+
await deleteDetachedForms([{id: '12345', name: 'BlogCreateForm'}], amplifyStudioClientMock);
|
|
75
|
+
expect(mockedDeleteForm).toBeCalledTimes(1);
|
|
76
|
+
expect(mockedDeleteForm).toHaveBeenCalledWith('12345');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -9,8 +9,10 @@ const utilsMock = utils as any;
|
|
|
9
9
|
|
|
10
10
|
utilsMock.shouldRenderComponents = jest.fn().mockReturnValue(true);
|
|
11
11
|
utilsMock.notifyMissingPackages = jest.fn().mockReturnValue(true);
|
|
12
|
+
utilsMock.getAmplifyDataSchema = jest.fn().mockReturnValue(({}));
|
|
13
|
+
|
|
12
14
|
jest.mock('../commands/utils/featureFlags', () => ({
|
|
13
|
-
getTransformerVersion: jest.fn().
|
|
15
|
+
getTransformerVersion: jest.fn().mockReturnValue(2),
|
|
14
16
|
}));
|
|
15
17
|
|
|
16
18
|
describe('can generate components', () => {
|
|
@@ -37,6 +39,13 @@ describe('can generate components', () => {
|
|
|
37
39
|
name: 'testSchema',
|
|
38
40
|
schemaVersion: '1.0',
|
|
39
41
|
},
|
|
42
|
+
{
|
|
43
|
+
resultType: 'FAILURE',
|
|
44
|
+
schemaName: 'testSchema',
|
|
45
|
+
name: 'testSchema',
|
|
46
|
+
schemaVersion: '1.0',
|
|
47
|
+
schema: { id: 'f-123456', name: 'testSchema' },
|
|
48
|
+
},
|
|
40
49
|
],
|
|
41
50
|
};
|
|
42
51
|
mockedExport = jest.fn().mockReturnValue({
|
|
@@ -70,22 +79,15 @@ describe('can generate components', () => {
|
|
|
70
79
|
utilsMock.getAmplifyDataSchema = jest.fn().mockReturnValue(undefined);
|
|
71
80
|
utilsMock.generateAmplifyUiBuilderIndexFile = jest.fn().mockReturnValue(true);
|
|
72
81
|
utilsMock.generateAmplifyUiBuilderUtilFile = jest.fn().mockReturnValue(true);
|
|
82
|
+
utilsMock.deleteDetachedForms = jest.fn();
|
|
73
83
|
});
|
|
74
84
|
|
|
75
85
|
it('runs generateComponents', async () => {
|
|
76
|
-
await run(context);
|
|
86
|
+
await run(context, 'PostPull');
|
|
77
87
|
expect(mockedExport).toBeCalledTimes(3);
|
|
78
88
|
expect(utilsMock.generateUiBuilderComponents).toBeCalledTimes(1);
|
|
79
89
|
expect(utilsMock.generateUiBuilderThemes).toBeCalledTimes(1);
|
|
80
90
|
expect(utilsMock.generateUiBuilderForms).toBeCalledTimes(1);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
it('does not run generateComponents if not Amplify Admin app', async () => {
|
|
84
|
-
utilsMock.shouldRenderComponents = jest.fn().mockReturnValue(false);
|
|
85
|
-
await run(context);
|
|
86
|
-
expect(mockedExport).toBeCalledTimes(0);
|
|
87
|
-
expect(utilsMock.generateUiBuilderComponents).toBeCalledTimes(0);
|
|
88
|
-
expect(utilsMock.generateUiBuilderThemes).toBeCalledTimes(0);
|
|
89
|
-
expect(utilsMock.generateUiBuilderForms).toBeCalledTimes(0);
|
|
91
|
+
expect(utilsMock.deleteDetachedForms).toBeCalledTimes(1);
|
|
90
92
|
});
|
|
91
93
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { run as runPostPull } from '../event-handlers/handle-PostPull';
|
|
2
|
+
import { run as runPostPush } from '../event-handlers/handle-PostPush';
|
|
3
|
+
import { run as generateComponents } from '../commands/generateComponents';
|
|
4
|
+
|
|
5
|
+
jest.mock('../commands/generateComponents');
|
|
6
|
+
|
|
7
|
+
describe('can handle events', () => {
|
|
8
|
+
let context: any;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
context = {
|
|
12
|
+
amplify: {
|
|
13
|
+
invokePluginMethod: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('handles postPull', async () => {
|
|
19
|
+
await runPostPull(context);
|
|
20
|
+
expect(generateComponents).toHaveBeenCalledWith(context, 'PostPull');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('handles postPush', async () => {
|
|
24
|
+
await runPostPush(context);
|
|
25
|
+
expect(generateComponents).toHaveBeenCalledWith(context, 'PostPush');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// aws-sdk, amplify-cli-core, amplify-prompts are in package dependencies
|
|
2
|
+
import aws from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import { Form } from 'aws-sdk/clients/amplifyuibuilder'; // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
+
import { printer } from 'amplify-prompts'; // eslint-disable-line import/no-extraneous-dependencies
|
|
5
|
+
import * as utils from '../commands/utils';
|
|
6
|
+
import { prePushHandler } from '../utils/prePushHandler';
|
|
7
|
+
|
|
8
|
+
jest.mock('../commands/utils');
|
|
9
|
+
jest.mock('amplify-cli-core');
|
|
10
|
+
jest.mock('../commands/utils/featureFlags', () => ({
|
|
11
|
+
getTransformerVersion: jest.fn().mockImplementation(() => 2),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const awsMock = aws as any;
|
|
15
|
+
const utilsMock = utils as any;
|
|
16
|
+
|
|
17
|
+
utilsMock.shouldRenderComponents = jest.fn().mockImplementation(() => true);
|
|
18
|
+
|
|
19
|
+
describe('handlePrePush', () => {
|
|
20
|
+
let context: any;
|
|
21
|
+
let mockedExport: jest.Mock<any, any>;
|
|
22
|
+
let exportedForms: Form[];
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
context = {
|
|
26
|
+
amplify: {
|
|
27
|
+
invokePluginMethod: () => ({ models: { Comment: {} } }),
|
|
28
|
+
},
|
|
29
|
+
input: {
|
|
30
|
+
options: {
|
|
31
|
+
appId: 'testAppId',
|
|
32
|
+
envName: 'testEnvName',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
exportedForms = [
|
|
38
|
+
{
|
|
39
|
+
name: 'BlogCreateForm',
|
|
40
|
+
formActionType: 'create',
|
|
41
|
+
fields: {},
|
|
42
|
+
style: {},
|
|
43
|
+
schemaVersion: '1.0',
|
|
44
|
+
sectionalElements: {},
|
|
45
|
+
appId: '1234',
|
|
46
|
+
environmentName: 'staging',
|
|
47
|
+
dataType: {
|
|
48
|
+
dataSourceType: 'DataStore',
|
|
49
|
+
dataTypeName: 'Blog',
|
|
50
|
+
},
|
|
51
|
+
id: '12345',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'BlogUpdateForm',
|
|
55
|
+
formActionType: 'update',
|
|
56
|
+
fields: {},
|
|
57
|
+
style: {},
|
|
58
|
+
schemaVersion: '1.0',
|
|
59
|
+
sectionalElements: {},
|
|
60
|
+
appId: '1234',
|
|
61
|
+
environmentName: 'staging',
|
|
62
|
+
dataType: {
|
|
63
|
+
dataSourceType: 'DataStore',
|
|
64
|
+
dataTypeName: 'Blog',
|
|
65
|
+
},
|
|
66
|
+
id: '12345',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
mockedExport = jest.fn(() => ({
|
|
71
|
+
entities: exportedForms,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
awsMock.AmplifyUIBuilder = jest.fn(() => ({
|
|
75
|
+
exportForms: jest.fn(() => ({
|
|
76
|
+
promise: () => mockedExport(),
|
|
77
|
+
})),
|
|
78
|
+
getMetadata: jest.fn(() => ({
|
|
79
|
+
promise: jest.fn(() => ({
|
|
80
|
+
features: {
|
|
81
|
+
autoGenerateForms: 'true',
|
|
82
|
+
autoGenerateViews: 'true',
|
|
83
|
+
},
|
|
84
|
+
})),
|
|
85
|
+
})),
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('runs handlePrePush', async () => {
|
|
90
|
+
utilsMock.isFormDetachedFromModel = jest.fn().mockImplementation(() => true);
|
|
91
|
+
utilsMock.isFormSchemaCustomized = jest.fn().mockImplementation(() => true);
|
|
92
|
+
const spy = jest.spyOn(printer, 'warn');
|
|
93
|
+
await prePushHandler(context);
|
|
94
|
+
expect(spy).toHaveBeenCalledWith('The following forms will no longer be available because the connected data model no longer exists: BlogCreateForm, BlogUpdateForm');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('runs handlePrePush without schema', async () => {
|
|
98
|
+
context.amplify.invokePluginMethod = jest.fn()
|
|
99
|
+
.mockImplementation(() => ({ models: { Comment: {} } }))
|
|
100
|
+
.mockImplementation(() => (null));
|
|
101
|
+
const spy = jest.spyOn(printer, 'debug');
|
|
102
|
+
await prePushHandler(context);
|
|
103
|
+
expect(spy).toHaveBeenCalledWith('Local schema not found');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import {
|
|
3
|
-
$TSContext, AmplifyCategories, AmplifySupportedService, CloudformationProviderFacade,
|
|
4
|
-
} from 'amplify-cli-core';
|
|
2
|
+
import {$TSContext} from 'amplify-cli-core';
|
|
5
3
|
import aws from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies
|
|
6
4
|
import { shouldRenderComponents } from '../commands/utils/shouldRenderComponents';
|
|
7
5
|
|
|
@@ -10,26 +8,21 @@ const awsMock = aws as any;
|
|
|
10
8
|
jest.mock('amplify-cli-core', () => ({
|
|
11
9
|
...jest.requireActual('amplify-cli-core'),
|
|
12
10
|
stateManager: {
|
|
13
|
-
|
|
14
|
-
[
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
getProjectConfig: jest.fn().mockReturnValue({
|
|
12
|
+
providers: ['awscloudformation'],
|
|
13
|
+
frontend: 'javascript',
|
|
14
|
+
javascript: {
|
|
15
|
+
framework: 'react',
|
|
18
16
|
},
|
|
19
|
-
})
|
|
20
|
-
// eslint-disable-next-line spellcheck/spell-checker
|
|
21
|
-
getCLIJSON: jest.fn().mockReturnValue({ features: { graphqltransformer: { transformerversion: 2 } } }),
|
|
22
|
-
setCLIJSON: jest.fn(),
|
|
23
|
-
},
|
|
24
|
-
FeatureFlags: {
|
|
25
|
-
getBoolean: () => true,
|
|
26
|
-
getNumber: jest.fn().mockReturnValue(2),
|
|
27
|
-
reloadValues: jest.fn(),
|
|
17
|
+
})
|
|
28
18
|
},
|
|
29
19
|
}));
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
|
|
21
|
+
jest.mock('../clients', () => ({
|
|
22
|
+
AmplifyStudioClient: {
|
|
23
|
+
isAmplifyApp: jest.fn().mockReturnValue(true)
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
33
26
|
|
|
34
27
|
describe('should render components', () => {
|
|
35
28
|
let context: $TSContext | any;
|
|
@@ -47,9 +40,6 @@ describe('should render components', () => {
|
|
|
47
40
|
})),
|
|
48
41
|
}));
|
|
49
42
|
context = {
|
|
50
|
-
amplify: {
|
|
51
|
-
invokePluginMethod: () => ({}),
|
|
52
|
-
},
|
|
53
43
|
input: {
|
|
54
44
|
options: {
|
|
55
45
|
'no-codegen': false,
|
|
@@ -57,38 +47,61 @@ describe('should render components', () => {
|
|
|
57
47
|
envName: 'testEnvName',
|
|
58
48
|
},
|
|
59
49
|
},
|
|
60
|
-
exeInfo: {
|
|
61
|
-
projectConfig: {
|
|
62
|
-
providers: ['awscloudformation'],
|
|
63
|
-
frontend: 'javascript',
|
|
64
|
-
javascript: {
|
|
65
|
-
framework: 'react',
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
50
|
};
|
|
70
51
|
});
|
|
52
|
+
|
|
71
53
|
it('works with a valid config', async () => {
|
|
72
54
|
const shouldIt = await shouldRenderComponents(context);
|
|
73
55
|
expect(shouldIt).toBe(true);
|
|
74
56
|
});
|
|
57
|
+
|
|
75
58
|
it("doesn't work if --no-codegen flag is set", async () => {
|
|
76
59
|
context.input.options['no-codegen'] = true;
|
|
77
60
|
const shouldIt = await shouldRenderComponents(context);
|
|
78
61
|
expect(shouldIt).toBe(false);
|
|
79
62
|
});
|
|
63
|
+
|
|
80
64
|
it("doesn't work if provider is not awscloudformation", async () => {
|
|
81
|
-
|
|
65
|
+
jest.mock('amplify-cli-core', () => ({
|
|
66
|
+
stateManager: {
|
|
67
|
+
getProjectConfig: jest.fn().mockReturnValue({
|
|
68
|
+
providers: [],
|
|
69
|
+
frontend: 'javascript',
|
|
70
|
+
javascript: {
|
|
71
|
+
framework: 'react',
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
82
76
|
const shouldIt = await shouldRenderComponents(context);
|
|
83
77
|
expect(shouldIt).toBe(false);
|
|
84
78
|
});
|
|
79
|
+
|
|
85
80
|
it('should return false if frontend is ios', async () => {
|
|
86
|
-
|
|
81
|
+
jest.mock('amplify-cli-core', () => ({
|
|
82
|
+
stateManager: {
|
|
83
|
+
getProjectConfig: jest.fn().mockReturnValue({
|
|
84
|
+
providers: [],
|
|
85
|
+
frontend: 'ios',
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
87
89
|
const shouldIt = await shouldRenderComponents(context);
|
|
88
90
|
expect(shouldIt).toBe(false);
|
|
89
91
|
});
|
|
92
|
+
|
|
90
93
|
it('should return false if frontend is vue', async () => {
|
|
91
|
-
|
|
94
|
+
jest.mock('amplify-cli-core', () => ({
|
|
95
|
+
stateManager: {
|
|
96
|
+
getProjectConfig: jest.fn().mockReturnValue({
|
|
97
|
+
providers: [],
|
|
98
|
+
frontend: 'javascript',
|
|
99
|
+
javascript: {
|
|
100
|
+
framework: 'vue',
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
}));
|
|
92
105
|
const shouldIt = await shouldRenderComponents(context);
|
|
93
106
|
expect(shouldIt).toBe(false);
|
|
94
107
|
});
|
|
@@ -262,7 +262,7 @@ describe('should sync amplify ui builder components', () => {
|
|
|
262
262
|
const forms = generateUiBuilderForms(context, [{}, {}]);
|
|
263
263
|
expect(forms.every(form => form.resultType === 'FAILURE')).toBeTruthy();
|
|
264
264
|
});
|
|
265
|
-
it('can generate uibuilder forms from data schema if
|
|
265
|
+
it('can generate uibuilder forms from data schema if autogenerate true', async () => {
|
|
266
266
|
createUiBuilderComponentDependencyMock.createUiBuilderForm = jest.fn().mockImplementation(() => ({}));
|
|
267
267
|
const forms = generateUiBuilderForms(context, [], exampleSchema, true);
|
|
268
268
|
expect(forms.every(form => form.resultType === 'SUCCESS')).toBeTruthy();
|
|
@@ -270,7 +270,7 @@ describe('should sync amplify ui builder components', () => {
|
|
|
270
270
|
expect(forms.length).toEqual(2);
|
|
271
271
|
});
|
|
272
272
|
|
|
273
|
-
it('should not
|
|
273
|
+
it('should not autogenerate forms for join tables or unsupported models', async () => {
|
|
274
274
|
expect(Object.keys(exampleSchema.models)).toStrictEqual(['Author', 'JoinTable', 'EmptyModel']);
|
|
275
275
|
createUiBuilderComponentDependencyMock.createUiBuilderForm = jest.fn().mockImplementation(
|
|
276
276
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -282,7 +282,7 @@ describe('should sync amplify ui builder components', () => {
|
|
|
282
282
|
expect(forms.map(f => (f.schema as any).name)).toStrictEqual(['Author', 'Author']);
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
-
it('does not generate uibuilder forms from data schema if
|
|
285
|
+
it('does not generate uibuilder forms from data schema if autogenerate false', async () => {
|
|
286
286
|
createUiBuilderComponentDependencyMock.createUiBuilderForm = jest.fn().mockImplementation(() => ({}));
|
|
287
287
|
const forms = generateUiBuilderForms(context, [], exampleSchema, false);
|
|
288
288
|
expect(forms.length).toEqual(0);
|
|
@@ -230,6 +230,19 @@ export default class AmplifyStudioClient {
|
|
|
230
230
|
}
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
+
deleteForm = async (formId: string, envName?: string, appId?: string): Promise<void> => {
|
|
234
|
+
const environmentName = envName || this.#envName;
|
|
235
|
+
const resolvedAppId = appId || this.#appId;
|
|
236
|
+
try {
|
|
237
|
+
await this.#amplifyUiBuilder
|
|
238
|
+
.deleteForm({ id: formId, environmentName, appId: resolvedAppId })
|
|
239
|
+
.promise();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
printer.debug(err.toString());
|
|
242
|
+
throw err;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
233
246
|
getModels = async (resourceName: string, envName?: string, appId?: string): Promise<string | undefined> => {
|
|
234
247
|
try {
|
|
235
248
|
const environmentName = envName || this.#envName;
|
|
@@ -11,7 +11,7 @@ export const run = async (context: $TSContext): Promise<void> => {
|
|
|
11
11
|
const args = extractArgs(context);
|
|
12
12
|
const sourceEnvName = args.sourceEnvName ? args.sourceEnvName : context.exeInfo.sourceEnvName;
|
|
13
13
|
const newEnvName = args.newEnvName ? args.newEnvName : context.exeInfo.localEnvInfo.envName;
|
|
14
|
-
const appId = args.appId ?? context.exeInfo
|
|
14
|
+
const appId = args.appId ?? context.exeInfo?.teamProviderInfo?.[sourceEnvName]?.awscloudformation?.AmplifyAppId;
|
|
15
15
|
const studioClient = await AmplifyStudioClient.setClientInfo(context, sourceEnvName, appId);
|
|
16
16
|
|
|
17
17
|
const [existingComponents, existingComponentsNewEnv] = await Promise.all([
|
|
@@ -13,12 +13,15 @@ import {
|
|
|
13
13
|
generateAmplifyUiBuilderIndexFile,
|
|
14
14
|
generateUiBuilderForms,
|
|
15
15
|
generateAmplifyUiBuilderUtilFile,
|
|
16
|
+
isStudioForm,
|
|
17
|
+
isFormDetachedFromModel,
|
|
18
|
+
deleteDetachedForms,
|
|
16
19
|
} from './utils';
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Pulls ui components from Studio backend and generates the code in the user's file system
|
|
20
23
|
*/
|
|
21
|
-
export const run = async (context: $TSContext): Promise<void> => {
|
|
24
|
+
export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull'): Promise<void> => {
|
|
22
25
|
if (!(await shouldRenderComponents(context))) {
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
@@ -32,9 +35,9 @@ export const run = async (context: $TSContext): Promise<void> => {
|
|
|
32
35
|
getAmplifyDataSchema(studioClient),
|
|
33
36
|
]);
|
|
34
37
|
|
|
35
|
-
const
|
|
38
|
+
const nothingWouldAutogenerate = !dataSchema || !studioClient.metadata.autoGenerateForms || !studioClient.isGraphQLSupported;
|
|
36
39
|
|
|
37
|
-
if (
|
|
40
|
+
if (nothingWouldAutogenerate && [componentSchemas, themeSchemas, formSchemas].every(group => !group.entities.length)) {
|
|
38
41
|
printer.debug('Skipping UI component generation since none are found.');
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
@@ -44,13 +47,18 @@ export const run = async (context: $TSContext): Promise<void> => {
|
|
|
44
47
|
component: generateUiBuilderComponents(context, componentSchemas.entities, dataSchema),
|
|
45
48
|
theme: generateUiBuilderThemes(context, themeSchemas.entities),
|
|
46
49
|
form: generateUiBuilderForms(
|
|
47
|
-
context,
|
|
50
|
+
context,
|
|
51
|
+
formSchemas.entities,
|
|
52
|
+
dataSchema,
|
|
53
|
+
studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported,
|
|
48
54
|
),
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
const successfulSchemas: StudioSchema[] = [];
|
|
58
|
+
const detachedForms: {id: string, name: string}[] = [];
|
|
52
59
|
let hasSuccessfulForm = false;
|
|
53
60
|
const failedResponseNames: string[] = [];
|
|
61
|
+
const modelNames = dataSchema?.models ? new Set(Object.keys(dataSchema.models)) : new Set<string>();
|
|
54
62
|
|
|
55
63
|
Object.entries(generatedResults).forEach(([key, results]) => {
|
|
56
64
|
results.forEach(result => {
|
|
@@ -60,6 +68,22 @@ export const run = async (context: $TSContext): Promise<void> => {
|
|
|
60
68
|
hasSuccessfulForm = true;
|
|
61
69
|
}
|
|
62
70
|
} else {
|
|
71
|
+
const failedSchema = result.schema;
|
|
72
|
+
/**
|
|
73
|
+
* A form resource may fail to generate because it's DataStore model type
|
|
74
|
+
* no longer exists.
|
|
75
|
+
*/
|
|
76
|
+
if (
|
|
77
|
+
isStudioForm(failedSchema)
|
|
78
|
+
&& failedSchema.id
|
|
79
|
+
&& dataSchema
|
|
80
|
+
&& eventType === 'PostPush'
|
|
81
|
+
&& isFormDetachedFromModel(failedSchema, modelNames)
|
|
82
|
+
) {
|
|
83
|
+
// Don't need to add form to failedResponseNames if it is going to be deleted
|
|
84
|
+
detachedForms.push({id: failedSchema.id, name: failedSchema.name});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
63
87
|
failedResponseNames.push(result.schemaName);
|
|
64
88
|
}
|
|
65
89
|
});
|
|
@@ -88,6 +112,8 @@ export const run = async (context: $TSContext): Promise<void> => {
|
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
notifyMissingPackages(context);
|
|
115
|
+
|
|
116
|
+
deleteDetachedForms(detachedForms, studioClient);
|
|
91
117
|
} catch (e) {
|
|
92
118
|
printer.debug(e);
|
|
93
119
|
spinner.fail('Failed to sync UI components');
|
|
@@ -145,7 +145,9 @@ export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas:
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
try {
|
|
148
|
-
|
|
148
|
+
if(schemas.length) {
|
|
149
|
+
rendererManager.renderSchemaToTemplate(schemas);
|
|
150
|
+
}
|
|
149
151
|
} catch (e) {
|
|
150
152
|
printer.debug(e);
|
|
151
153
|
printer.debug('Failed to generate component index file');
|