@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,211 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
// You have a misspelled word: Renderer on Identifier
|
|
3
|
-
import {
|
|
4
|
-
StudioTemplateRendererManager,
|
|
5
|
-
StudioComponent,
|
|
6
|
-
StudioTheme,
|
|
7
|
-
StudioTemplateRendererFactory,
|
|
8
|
-
StudioTemplateRenderer,
|
|
9
|
-
FrameworkOutputManager,
|
|
10
|
-
RenderTextComponentResponse,
|
|
11
|
-
GenericDataSchema,
|
|
12
|
-
getGenericFromDataStore,
|
|
13
|
-
StudioForm,
|
|
14
|
-
StudioSchema,
|
|
15
|
-
FormFeatureFlags,
|
|
16
|
-
} from '@aws-amplify/codegen-ui';
|
|
17
|
-
import {
|
|
18
|
-
AmplifyRenderer,
|
|
19
|
-
AmplifyFormRenderer,
|
|
20
|
-
ReactThemeStudioTemplateRenderer,
|
|
21
|
-
ReactIndexStudioTemplateRenderer,
|
|
22
|
-
ModuleKind,
|
|
23
|
-
ScriptTarget,
|
|
24
|
-
ScriptKind,
|
|
25
|
-
UtilTemplateType,
|
|
26
|
-
ReactUtilsStudioTemplateRenderer,
|
|
27
|
-
ReactThemeStudioTemplateRendererOptions,
|
|
28
|
-
} from '@aws-amplify/codegen-ui-react';
|
|
1
|
+
import { GenericDataSchema, getGenericFromDataStore } from '@aws-amplify/codegen-ui';
|
|
29
2
|
import { printer } from '@aws-amplify/amplify-prompts';
|
|
30
3
|
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
31
|
-
import { getUiBuilderComponentsPath } from './getUiBuilderComponentsPath';
|
|
32
4
|
import { ModelIntrospectionSchema } from '@aws-amplify/appsync-modelgen-plugin';
|
|
33
5
|
|
|
34
|
-
const config = {
|
|
35
|
-
module: ModuleKind.ES2020,
|
|
36
|
-
target: ScriptTarget.ES2020,
|
|
37
|
-
script: ScriptKind.JSX,
|
|
38
|
-
renderTypeDeclarations: true,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Writes component file to the work space
|
|
43
|
-
*/
|
|
44
|
-
export const createUiBuilderComponent = (context: $TSContext, schema: StudioComponent, dataSchema?: GenericDataSchema): StudioComponent => {
|
|
45
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
46
|
-
const rendererFactory = new StudioTemplateRendererFactory(
|
|
47
|
-
(component: StudioComponent) => new AmplifyRenderer(component as StudioComponent, config, dataSchema),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const outputPathDir = uiBuilderComponentsPath;
|
|
51
|
-
|
|
52
|
-
const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
|
|
53
|
-
outputPathDir,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
rendererManager.renderSchemaToTemplate(schema);
|
|
57
|
-
return schema;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Writes theme file to the work space
|
|
62
|
-
*/
|
|
63
|
-
export const createUiBuilderTheme = (
|
|
64
|
-
context: $TSContext,
|
|
65
|
-
schema: StudioTheme,
|
|
66
|
-
options?: ReactThemeStudioTemplateRendererOptions,
|
|
67
|
-
): StudioTheme => {
|
|
68
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
69
|
-
const rendererFactory = new StudioTemplateRendererFactory(
|
|
70
|
-
(component: StudioTheme) =>
|
|
71
|
-
new ReactThemeStudioTemplateRenderer(component, config, options) as unknown as StudioTemplateRenderer<
|
|
72
|
-
unknown,
|
|
73
|
-
StudioTheme,
|
|
74
|
-
FrameworkOutputManager<unknown>,
|
|
75
|
-
RenderTextComponentResponse
|
|
76
|
-
>,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const outputPathDir = uiBuilderComponentsPath;
|
|
80
|
-
|
|
81
|
-
const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
|
|
82
|
-
outputPathDir,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
rendererManager.renderSchemaToTemplate(schema);
|
|
87
|
-
return schema;
|
|
88
|
-
} catch (e) {
|
|
89
|
-
printer.debug(e);
|
|
90
|
-
printer.debug('Skipping invalid theme with schema');
|
|
91
|
-
printer.debug(JSON.stringify(schema, null, 2));
|
|
92
|
-
throw e;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Writes form file to the work space
|
|
98
|
-
*/
|
|
99
|
-
export const createUiBuilderForm = (
|
|
100
|
-
context: $TSContext,
|
|
101
|
-
schema: StudioForm,
|
|
102
|
-
dataSchema?: GenericDataSchema,
|
|
103
|
-
formFeatureFlags?: FormFeatureFlags,
|
|
104
|
-
): StudioForm => {
|
|
105
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
106
|
-
const rendererFactory = new StudioTemplateRendererFactory(
|
|
107
|
-
(form: StudioForm) =>
|
|
108
|
-
new AmplifyFormRenderer(form, dataSchema, config, formFeatureFlags) as unknown as StudioTemplateRenderer<
|
|
109
|
-
unknown,
|
|
110
|
-
StudioForm,
|
|
111
|
-
FrameworkOutputManager<unknown>,
|
|
112
|
-
RenderTextComponentResponse
|
|
113
|
-
>,
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const outputPathDir = uiBuilderComponentsPath;
|
|
117
|
-
|
|
118
|
-
const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
|
|
119
|
-
outputPathDir,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
rendererManager.renderSchemaToTemplate(schema);
|
|
124
|
-
return schema;
|
|
125
|
-
} catch (e) {
|
|
126
|
-
printer.debug(e);
|
|
127
|
-
printer.debug('Skipping invalid form with schema');
|
|
128
|
-
printer.debug(JSON.stringify(schema, null, 2));
|
|
129
|
-
throw e;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Writes index file to the work space
|
|
135
|
-
*/
|
|
136
|
-
export const generateAmplifyUiBuilderIndexFile = (context: $TSContext, schemas: StudioSchema[]): void => {
|
|
137
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
138
|
-
const rendererFactory = new StudioTemplateRendererFactory(
|
|
139
|
-
(schema: StudioSchema[]) =>
|
|
140
|
-
new ReactIndexStudioTemplateRenderer(schema, config) as unknown as StudioTemplateRenderer<
|
|
141
|
-
unknown,
|
|
142
|
-
StudioSchema[],
|
|
143
|
-
FrameworkOutputManager<unknown>,
|
|
144
|
-
RenderTextComponentResponse
|
|
145
|
-
>,
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const outputPathDir = uiBuilderComponentsPath;
|
|
149
|
-
|
|
150
|
-
const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
|
|
151
|
-
outputPathDir,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
if (schemas.length) {
|
|
156
|
-
rendererManager.renderSchemaToTemplate(schemas);
|
|
157
|
-
}
|
|
158
|
-
} catch (e) {
|
|
159
|
-
printer.debug(e);
|
|
160
|
-
printer.debug('Failed to generate component index file');
|
|
161
|
-
throw e;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
type UtilFileChecks = {
|
|
166
|
-
hasForms: boolean;
|
|
167
|
-
hasViews: boolean;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Writes utils file to the work space
|
|
172
|
-
*/
|
|
173
|
-
export const generateAmplifyUiBuilderUtilFile = (context: $TSContext, { hasForms, hasViews }: UtilFileChecks): void => {
|
|
174
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
175
|
-
const rendererFactory = new StudioTemplateRendererFactory(
|
|
176
|
-
(utils: UtilTemplateType[]) => new ReactUtilsStudioTemplateRenderer(utils, config),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
const outputPathDir = uiBuilderComponentsPath;
|
|
180
|
-
|
|
181
|
-
const rendererManager = new StudioTemplateRendererManager(rendererFactory, {
|
|
182
|
-
outputPathDir,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const utils = new Set<UtilTemplateType>();
|
|
186
|
-
|
|
187
|
-
if (hasForms) {
|
|
188
|
-
utils.add('validation');
|
|
189
|
-
utils.add('formatter');
|
|
190
|
-
utils.add('fetchByPath');
|
|
191
|
-
utils.add('processFile');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (hasViews) {
|
|
195
|
-
utils.add('formatter');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
if (utils.size) {
|
|
200
|
-
rendererManager.renderSchemaToTemplate([...utils]);
|
|
201
|
-
}
|
|
202
|
-
} catch (e) {
|
|
203
|
-
printer.debug(e);
|
|
204
|
-
printer.debug('Failed to generate component index file');
|
|
205
|
-
throw e;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
6
|
/**
|
|
210
7
|
* If models are available, they will be populated in the models field of the returned object.
|
|
211
8
|
* If they're not available, it will return undefined
|
|
@@ -224,25 +21,3 @@ export const getAmplifyDataSchema = async (context: $TSContext): Promise<Generic
|
|
|
224
21
|
return undefined;
|
|
225
22
|
}
|
|
226
23
|
};
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* generates base create/update froms from names
|
|
230
|
-
*/
|
|
231
|
-
export const generateBaseForms = (modelMap: { [model: string]: Set<'create' | 'update'> }): StudioForm[] => {
|
|
232
|
-
const getSchema = (name: string, type: 'create' | 'update'): StudioForm => ({
|
|
233
|
-
name: `${name}${type === 'create' ? 'CreateForm' : 'UpdateForm'}`,
|
|
234
|
-
formActionType: type,
|
|
235
|
-
dataType: { dataSourceType: 'DataStore', dataTypeName: name },
|
|
236
|
-
fields: {},
|
|
237
|
-
sectionalElements: {},
|
|
238
|
-
style: {},
|
|
239
|
-
cta: {},
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const schemas: StudioForm[] = [];
|
|
243
|
-
|
|
244
|
-
Object.entries(modelMap).forEach(([name, set]) => {
|
|
245
|
-
set.forEach((type) => schemas.push(getSchema(name, type)));
|
|
246
|
-
});
|
|
247
|
-
return schemas;
|
|
248
|
-
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { StudioForm, StudioSchema } from '@aws-amplify/codegen-ui';
|
|
2
1
|
import { Form } from 'aws-sdk/clients/amplifyuibuilder';
|
|
3
2
|
import { AmplifyStudioClient } from '../../clients';
|
|
4
3
|
import { printer } from '@aws-amplify/amplify-prompts';
|
|
@@ -7,7 +6,7 @@ import asyncPool from 'tiny-async-pool';
|
|
|
7
6
|
/**
|
|
8
7
|
* Does the form's reference DataStore type exist in the list of models
|
|
9
8
|
*/
|
|
10
|
-
export const isFormDetachedFromModel = (formSchema:
|
|
9
|
+
export const isFormDetachedFromModel = (formSchema: Form, modelNames: Set<string>): boolean => {
|
|
11
10
|
return formSchema.dataType.dataSourceType === 'DataStore' && !modelNames.has(formSchema.dataType.dataTypeName);
|
|
12
11
|
};
|
|
13
12
|
|
|
@@ -16,7 +15,7 @@ export const isFormDetachedFromModel = (formSchema: StudioForm | Form, modelName
|
|
|
16
15
|
* @param schema The schema to test
|
|
17
16
|
* @returns boolean
|
|
18
17
|
*/
|
|
19
|
-
export const isFormSchemaCustomized = (formSchema:
|
|
18
|
+
export const isFormSchemaCustomized = (formSchema: Form): boolean => {
|
|
20
19
|
const { fields, style, sectionalElements } = formSchema;
|
|
21
20
|
// Fields and sectionalElements just need an empty check
|
|
22
21
|
if (!isEmpty({ ...fields, ...sectionalElements })) {
|
|
@@ -31,7 +30,7 @@ export const isFormSchemaCustomized = (formSchema: StudioForm | Form): boolean =
|
|
|
31
30
|
/**
|
|
32
31
|
* Is the schema a StudioForm
|
|
33
32
|
*/
|
|
34
|
-
export const isStudioForm = (schema:
|
|
33
|
+
export const isStudioForm = (schema: Form | undefined): schema is Form => {
|
|
35
34
|
if (!schema) return false;
|
|
36
35
|
return 'formActionType' in schema;
|
|
37
36
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FieldConfig, Form } from 'aws-sdk/clients/amplifyuibuilder';
|
|
2
2
|
|
|
3
|
-
export const hasStorageField = (form:
|
|
3
|
+
export const hasStorageField = (form: Form): boolean => {
|
|
4
4
|
const result = Object.values(form.fields).some((field) => {
|
|
5
5
|
return isGenericFieldConfig(field) && field.inputType?.type === 'StorageField';
|
|
6
6
|
});
|
|
@@ -8,10 +8,6 @@ export const hasStorageField = (form: StudioForm): boolean => {
|
|
|
8
8
|
return result;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export const
|
|
12
|
-
return schema && Object.keys(schema).includes('fields');
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const isGenericFieldConfig = (config: StudioFormFieldConfig): config is StudioGenericFieldConfig => {
|
|
11
|
+
export const isGenericFieldConfig = (config: FieldConfig) => {
|
|
16
12
|
return !Object.keys(config).includes('excluded');
|
|
17
13
|
};
|
|
@@ -1,145 +1,232 @@
|
|
|
1
1
|
import { printer } from '@aws-amplify/amplify-prompts';
|
|
2
|
-
import {
|
|
2
|
+
import type { GenericDataSchema, GenericDataField, GenericDataRelationshipType } from '@aws-amplify/codegen-ui';
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
CodegenGenericDataEnum,
|
|
5
|
+
CodegenGenericDataFieldDataType,
|
|
6
|
+
CodegenGenericDataFields,
|
|
7
|
+
CodegenGenericDataModel,
|
|
8
|
+
CodegenGenericDataNonModel,
|
|
9
|
+
CodegenGenericDataRelationshipType,
|
|
10
|
+
CodegenJob,
|
|
11
|
+
CodegenJobGenericDataSchema,
|
|
12
|
+
} from 'aws-sdk/clients/amplifyuibuilder';
|
|
13
|
+
import fetch from 'node-fetch';
|
|
14
|
+
import { performance } from 'perf_hooks';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import asyncPool from 'tiny-async-pool';
|
|
18
|
+
|
|
19
|
+
const mapRelationshipTypeToCodegen = (
|
|
20
|
+
relationship: GenericDataRelationshipType | undefined,
|
|
21
|
+
): CodegenGenericDataRelationshipType | undefined => {
|
|
22
|
+
if (!relationship) return undefined;
|
|
23
|
+
|
|
24
|
+
switch (relationship.type) {
|
|
25
|
+
case 'HAS_MANY':
|
|
26
|
+
return {
|
|
27
|
+
type: 'HAS_MANY',
|
|
28
|
+
relatedModelFields: relationship.relatedModelFields,
|
|
29
|
+
canUnlinkAssociatedModel: !!relationship.canUnlinkAssociatedModel,
|
|
30
|
+
relatedJoinFieldName: relationship.relatedJoinFieldName,
|
|
31
|
+
relatedJoinTableName: relationship.relatedJoinTableName,
|
|
32
|
+
belongsToFieldOnRelatedModel: relationship.belongsToFieldOnRelatedModel,
|
|
33
|
+
relatedModelName: relationship.relatedModelName,
|
|
34
|
+
};
|
|
35
|
+
case 'HAS_ONE':
|
|
36
|
+
return {
|
|
37
|
+
type: 'HAS_ONE',
|
|
38
|
+
associatedFields: relationship.associatedFields,
|
|
39
|
+
isHasManyIndex: !!relationship.isHasManyIndex,
|
|
40
|
+
relatedModelName: relationship.relatedModelName,
|
|
41
|
+
};
|
|
42
|
+
case 'BELONGS_TO':
|
|
43
|
+
return {
|
|
44
|
+
type: 'BELONGS_TO',
|
|
45
|
+
associatedFields: relationship.associatedFields,
|
|
46
|
+
isHasManyIndex: !!relationship.isHasManyIndex,
|
|
47
|
+
relatedModelName: relationship.relatedModelName,
|
|
48
|
+
};
|
|
49
|
+
default:
|
|
50
|
+
throw new Error('Invalid relationship type');
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const mapDataFieldsToCodegen = (fields: { [fieldName: string]: GenericDataField }): CodegenGenericDataFields => {
|
|
55
|
+
const codegenFields: CodegenGenericDataFields = {};
|
|
56
|
+
|
|
57
|
+
Object.entries(fields).forEach(([fieldName, dataField]) => {
|
|
58
|
+
let dataType: CodegenGenericDataFieldDataType = '';
|
|
59
|
+
let dataTypeValue = '';
|
|
60
|
+
if (typeof dataField.dataType === 'object' && dataField.dataType !== null) {
|
|
61
|
+
if ('enum' in dataField.dataType) {
|
|
62
|
+
dataType = 'Enum';
|
|
63
|
+
dataTypeValue = dataField.dataType.enum;
|
|
64
|
+
} else if ('model' in dataField.dataType) {
|
|
65
|
+
dataType = 'Model';
|
|
66
|
+
dataTypeValue = dataField.dataType.model;
|
|
67
|
+
} else if ('nonModel' in dataField.dataType) {
|
|
68
|
+
dataType = 'NonModel';
|
|
69
|
+
dataTypeValue = dataField.dataType.nonModel;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
dataType = dataField.dataType;
|
|
73
|
+
dataTypeValue = dataField.dataType;
|
|
20
74
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
codegenFields[fieldName] = {
|
|
76
|
+
dataType: dataType,
|
|
77
|
+
dataTypeValue: dataTypeValue,
|
|
78
|
+
required: dataField.required,
|
|
79
|
+
readOnly: dataField.readOnly,
|
|
80
|
+
isArray: dataField.isArray,
|
|
26
81
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns instances of StudioComponent from the component schemas
|
|
32
|
-
*/
|
|
33
|
-
export const generateUiBuilderComponents = (
|
|
34
|
-
context: $TSContext,
|
|
35
|
-
componentSchemas: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
36
|
-
dataSchema?: GenericDataSchema,
|
|
37
|
-
): CodegenResponse<StudioComponent>[] => {
|
|
38
|
-
const componentResults = componentSchemas.map<CodegenResponse<StudioComponent>>((schema) => {
|
|
39
|
-
try {
|
|
40
|
-
const component = createUiBuilderComponent(context, schema, dataSchema);
|
|
41
|
-
return { resultType: 'SUCCESS', schema: component };
|
|
42
|
-
} catch (e) {
|
|
43
|
-
printer.debug(`Failure caught processing ${schema.name}`);
|
|
44
|
-
printer.debug(e);
|
|
45
|
-
return { resultType: 'FAILURE', schemaName: schema.name, error: e };
|
|
82
|
+
if (dataField.relationship) {
|
|
83
|
+
codegenFields[fieldName].relationship = mapRelationshipTypeToCodegen(dataField.relationship);
|
|
46
84
|
}
|
|
47
85
|
});
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
`Generated ${componentResults.filter((result) => result.resultType === 'SUCCESS').length} components in ${getUiBuilderComponentsPath(
|
|
51
|
-
context,
|
|
52
|
-
)}`,
|
|
53
|
-
);
|
|
54
|
-
return componentResults;
|
|
87
|
+
return codegenFields;
|
|
55
88
|
};
|
|
56
89
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
printer.debug(e);
|
|
72
|
-
return { resultType: 'FAILURE', schemaName: schema.name, error: e };
|
|
73
|
-
}
|
|
90
|
+
export const mapGenericDataSchemaToCodegen = (genericDataSchema: GenericDataSchema): CodegenJobGenericDataSchema => {
|
|
91
|
+
const { models, nonModels, enums, dataSourceType } = genericDataSchema;
|
|
92
|
+
const codegenModels: { [key: string]: CodegenGenericDataModel } = {};
|
|
93
|
+
const codegenNonModels: { [key: string]: CodegenGenericDataNonModel } = {};
|
|
94
|
+
const codegenEnums: { [key: string]: CodegenGenericDataEnum } = {};
|
|
95
|
+
|
|
96
|
+
Object.entries(models).forEach(([modelName, genericDataModel]) => {
|
|
97
|
+
const modelFields = mapDataFieldsToCodegen(genericDataModel.fields);
|
|
98
|
+
|
|
99
|
+
codegenModels[modelName] = {
|
|
100
|
+
isJoinTable: genericDataModel.isJoinTable,
|
|
101
|
+
primaryKeys: genericDataModel.primaryKeys,
|
|
102
|
+
fields: modelFields,
|
|
103
|
+
};
|
|
74
104
|
});
|
|
75
105
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
Object.entries(nonModels).forEach(([nonModelName, genericDataModel]) => {
|
|
107
|
+
const nonModelFields = mapDataFieldsToCodegen(genericDataModel.fields);
|
|
108
|
+
|
|
109
|
+
codegenNonModels[nonModelName] = {
|
|
110
|
+
fields: nonModelFields,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
Object.entries(enums).forEach(([enumName, genericEnum]) => {
|
|
115
|
+
codegenEnums[enumName] = {
|
|
116
|
+
values: genericEnum.values,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
models: codegenModels,
|
|
122
|
+
nonModels: codegenNonModels,
|
|
123
|
+
enums: codegenEnums,
|
|
124
|
+
dataSourceType,
|
|
125
|
+
};
|
|
80
126
|
};
|
|
81
127
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
function delay(durationMs: number): Promise<void> {
|
|
129
|
+
return new Promise((r) => setTimeout(() => r(), durationMs));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const waitForSucceededJob = async (getJob: () => Promise<CodegenJob>, { pollInterval }: { pollInterval: number }) => {
|
|
133
|
+
const startTime = performance.now();
|
|
134
|
+
// Adding env variable because if something happens and we need a longer timeout
|
|
135
|
+
// we will give the customer a chance to increase timeout as a workaround.
|
|
136
|
+
// Default timeout is 2 minutes for customers with thousands of components.
|
|
137
|
+
const waitTimeout = process.env.UI_BUILDER_CODEGENJOB_TIMEOUT ? parseInt(process.env.UI_BUILDER_CODEGENJOB_TIMEOUT) : 1000 * 60 * 2;
|
|
138
|
+
|
|
139
|
+
const endTime = startTime + waitTimeout;
|
|
140
|
+
|
|
141
|
+
while (performance.now() < endTime) {
|
|
142
|
+
const job = await getJob();
|
|
143
|
+
|
|
144
|
+
if (!job) {
|
|
145
|
+
printer.error('Codegen job not found');
|
|
146
|
+
throw new Error('Codegen job not found');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (job.status === 'failed') {
|
|
150
|
+
printer.error('Codegen job status is failed', { message: job.statusMessage });
|
|
151
|
+
throw new Error(job.statusMessage);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (job.status === 'succeeded') {
|
|
155
|
+
printer.debug(`Polling time: ${performance.now() - startTime}`);
|
|
156
|
+
|
|
157
|
+
return job;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await delay(pollInterval);
|
|
94
161
|
}
|
|
95
|
-
};
|
|
96
162
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*/
|
|
100
|
-
export const generateUiBuilderForms = (
|
|
101
|
-
context: $TSContext,
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
-
formSchemas: any[],
|
|
104
|
-
dataSchema?: GenericDataSchema,
|
|
105
|
-
autoGenerateForms?: boolean,
|
|
106
|
-
formFeatureFlags?: FormFeatureFlags,
|
|
107
|
-
): CodegenResponse<StudioForm>[] => {
|
|
108
|
-
const modelMap: { [model: string]: Set<'create' | 'update'> } = {};
|
|
109
|
-
if (dataSchema?.dataSourceType === 'DataStore' && autoGenerateForms) {
|
|
110
|
-
Object.entries(dataSchema.models).forEach(([name, model]) => {
|
|
111
|
-
if (checkIsSupportedAsForm(model, formFeatureFlags) && !model.isJoinTable) {
|
|
112
|
-
modelMap[name] = new Set(['create', 'update']);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
163
|
+
if (performance.now() > endTime) {
|
|
164
|
+
printer.error(`Codegen job never succeeded before timeout`);
|
|
115
165
|
}
|
|
116
|
-
|
|
166
|
+
|
|
167
|
+
throw new Error('Failed to return codegen job');
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const fetchWithRetries = async (url: string, retries = 3, delay = 300) => {
|
|
171
|
+
let retryCount = 0;
|
|
172
|
+
let retryDelay = delay;
|
|
173
|
+
|
|
174
|
+
while (retryCount < retries) {
|
|
117
175
|
try {
|
|
118
|
-
const
|
|
119
|
-
return
|
|
120
|
-
} catch (
|
|
121
|
-
printer.debug(`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
schemaName: schema.name,
|
|
126
|
-
schema,
|
|
127
|
-
error: e,
|
|
128
|
-
};
|
|
176
|
+
const response = await fetch(url);
|
|
177
|
+
return response;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
printer.debug(`Error fetching ${url}: ${error}`);
|
|
180
|
+
retryCount = retryCount + 1;
|
|
181
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
182
|
+
retryDelay = retryDelay * 2;
|
|
129
183
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
184
|
+
}
|
|
185
|
+
throw new Error('Fetch reached max number of retries without succeeding');
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const extractUIComponents = async (url: string, uiBuilderComponentsPath: string) => {
|
|
189
|
+
try {
|
|
190
|
+
if (!fs.existsSync(uiBuilderComponentsPath)) {
|
|
191
|
+
fs.mkdirSync(uiBuilderComponentsPath, { recursive: true });
|
|
134
192
|
}
|
|
135
|
-
return codegenForm(schema);
|
|
136
|
-
});
|
|
137
193
|
|
|
138
|
-
|
|
139
|
-
|
|
194
|
+
const response = await fetchWithRetries(url);
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new Error('Failed to download component manifest file');
|
|
197
|
+
}
|
|
198
|
+
const manifestFile = await (<
|
|
199
|
+
Promise<{ Output: { downloadUrl: string | undefined; fileName: string; schemaName: string; error: string | undefined }[] }>
|
|
200
|
+
>response.json());
|
|
201
|
+
|
|
202
|
+
const downloadComponent = async (output: { fileName: string; downloadUrl: string | undefined; error: string | undefined }) => {
|
|
203
|
+
if (output.downloadUrl && !output.error) {
|
|
204
|
+
try {
|
|
205
|
+
const response = await fetchWithRetries(output.downloadUrl);
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
printer.debug(`Failed to download ${output.fileName}`);
|
|
208
|
+
throw new Error(`Failed to download ${output.fileName}`);
|
|
209
|
+
}
|
|
210
|
+
return { content: await response.text(), error: undefined, fileName: output.fileName };
|
|
211
|
+
} catch (error) {
|
|
212
|
+
printer.debug(`Skipping ${output.fileName} because of an error downloading the component`);
|
|
213
|
+
return { error: `Failed to download ${output.fileName}`, content: undefined, fileName: output.fileName };
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
printer.debug(`Skipping ${output.fileName} because of an error generating the component`);
|
|
217
|
+
return { error: output.error, content: undefined, fileName: output.fileName };
|
|
218
|
+
}
|
|
219
|
+
};
|
|
140
220
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
221
|
+
for await (const downloaded of asyncPool(5, manifestFile.Output, downloadComponent)) {
|
|
222
|
+
if (downloaded.content) {
|
|
223
|
+
fs.writeFileSync(path.join(uiBuilderComponentsPath, downloaded.fileName), downloaded.content);
|
|
224
|
+
printer.debug(`Downloaded ${downloaded.fileName}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
printer.debug('ui-components downloaded successfully');
|
|
228
|
+
} catch (error) {
|
|
229
|
+
printer.error('failed to download ui-components');
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
145
232
|
};
|