@aws-amplify/amplify-util-uibuilder 1.10.1-sobkamil-test-0822.0 → 1.11.0-noinit-codegen.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 +10 -2
- package/package.json +6 -6
- package/src/__tests__/.eslintrc.js +0 -9
- package/src/__tests__/__snapshots__/syncAmplifyUiComponents.test.ts.snap +0 -46
- package/src/__tests__/cloneComponentsFromEnv.test.ts +0 -79
- package/src/__tests__/detachedFormCleanup.test.ts +0 -77
- package/src/__tests__/generateComponents.test.ts +0 -342
- package/src/__tests__/getAmplifyDataSchema.test.ts +0 -82
- package/src/__tests__/handleEvents.test.ts +0 -27
- package/src/__tests__/help.test.ts +0 -10
- package/src/__tests__/mock.json +0 -1
- package/src/__tests__/notifyMissingPackages.test.ts +0 -86
- package/src/__tests__/package.json +0 -3
- package/src/__tests__/prePushHandler.test.ts +0 -116
- package/src/__tests__/shouldRenderComponents.test.ts +0 -112
- package/src/__tests__/storageManager.test.ts +0 -45
- package/src/__tests__/syncAmplifyUiComponents.test.ts +0 -309
- package/src/__tests__/utils/getApiConfiguration.test.ts +0 -41
- package/src/__tests__/utils.ts +0 -72
- package/src/__tests__/version.test.ts +0 -10
- package/src/clients/amplify-studio-client.ts +0 -320
- package/src/clients/index.ts +0 -1
- package/src/commands/cloneComponentsFromEnv.ts +0 -53
- package/src/commands/generateComponents.ts +0 -125
- package/src/commands/help.ts +0 -8
- package/src/commands/utils/codegenResources.ts +0 -23
- package/src/commands/utils/detachedFormCleanup.ts +0 -54
- package/src/commands/utils/environmentHelpers.ts +0 -32
- package/src/commands/utils/extractArgs.ts +0 -25
- package/src/commands/utils/featureFlags.ts +0 -61
- package/src/commands/utils/getApiConfiguration.ts +0 -73
- package/src/commands/utils/getUiBuilderComponentsPath.ts +0 -21
- package/src/commands/utils/index.ts +0 -9
- package/src/commands/utils/notifyMissingPackages.ts +0 -47
- package/src/commands/utils/shouldRenderComponents.ts +0 -48
- package/src/commands/utils/storageManager.ts +0 -13
- package/src/commands/utils/syncAmplifyUiBuilderComponents.ts +0 -232
- package/src/commands/version.ts +0 -9
- package/src/event-handlers/handle-PostEnvAdd.ts +0 -16
- package/src/event-handlers/handle-PostPull.ts +0 -15
- package/src/event-handlers/handle-PostPush.ts +0 -15
- package/src/event-handlers/handle-PrePush.ts +0 -15
- package/src/index.ts +0 -26
- package/src/utils/prePushHandler.ts +0 -51
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { $TSContext, CloudformationProviderFacade } from '@aws-amplify/amplify-cli-core';
|
|
2
|
-
import type { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
|
|
3
|
-
import type { CreateComponentData, Component, Theme, Form, StartCodegenJobData } from 'aws-sdk/clients/amplifyuibuilder';
|
|
4
|
-
import { AmplifyUIBuilder, AmplifyBackend } from 'aws-sdk';
|
|
5
|
-
import { printer } from '@aws-amplify/amplify-prompts';
|
|
6
|
-
import { getAppId, getEnvName } from '../commands/utils/environmentHelpers';
|
|
7
|
-
import { getTransformerVersion } from '../commands/utils/featureFlags';
|
|
8
|
-
import { isDataStoreEnabled } from '@aws-amplify/amplify-category-api';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* studio client metadata
|
|
12
|
-
*/
|
|
13
|
-
export type StudioMetadata = {
|
|
14
|
-
autoGenerateForms: boolean;
|
|
15
|
-
autoGenerateViews: boolean;
|
|
16
|
-
formFeatureFlags: {
|
|
17
|
-
isRelationshipSupported: boolean;
|
|
18
|
-
isNonModelSupported: boolean;
|
|
19
|
-
};
|
|
20
|
-
isGraphQLEnabled: boolean;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Builds the Amplify Backend Client
|
|
25
|
-
*/
|
|
26
|
-
const buildAmplifyBackendClient = (awsConfigInfo: ServiceConfigurationOptions): AmplifyBackend => {
|
|
27
|
-
const awsConfig = { ...awsConfigInfo };
|
|
28
|
-
if (process.env.AMPLIFY_BACKEND_ENDPOINT) {
|
|
29
|
-
awsConfig.endpoint = process.env.AMPLIFY_BACKEND_ENDPOINT;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (process.env.AMPLIFY_BACKEND_REGION) {
|
|
33
|
-
awsConfig.region = process.env.AMPLIFY_BACKEND_REGION;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return new AmplifyBackend(awsConfig);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Builds the Amplify UIBuilder Client
|
|
41
|
-
*/
|
|
42
|
-
const buildAmplifyUiBuilderClient = (awsConfigInfo: ServiceConfigurationOptions): AmplifyUIBuilder => {
|
|
43
|
-
const awsConfig = { ...awsConfigInfo };
|
|
44
|
-
if (process.env.UI_BUILDER_ENDPOINT) {
|
|
45
|
-
awsConfig.endpoint = process.env.UI_BUILDER_ENDPOINT;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (process.env.UI_BUILDER_REGION) {
|
|
49
|
-
awsConfig.region = process.env.UI_BUILDER_REGION;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return new AmplifyUIBuilder(awsConfig);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* used for Amplify Studio related clients
|
|
57
|
-
*/
|
|
58
|
-
export default class AmplifyStudioClient {
|
|
59
|
-
#amplifyUiBuilder: AmplifyUIBuilder;
|
|
60
|
-
#amplifyBackend: AmplifyBackend;
|
|
61
|
-
#appId: string;
|
|
62
|
-
#envName: string;
|
|
63
|
-
metadata: StudioMetadata;
|
|
64
|
-
isGraphQLSupported = false;
|
|
65
|
-
isDataStoreEnabled = false;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* static function meant to check if given appId is studio enabled
|
|
69
|
-
*/
|
|
70
|
-
static isAmplifyApp = async (context: $TSContext, appId: string): Promise<boolean> => {
|
|
71
|
-
try {
|
|
72
|
-
const { isAdminApp } = await CloudformationProviderFacade.isAmplifyAdminApp(context, appId);
|
|
73
|
-
return isAdminApp;
|
|
74
|
-
} catch (err) {
|
|
75
|
-
// return false is admin app failed check
|
|
76
|
-
// this means we wont run codegen-ui
|
|
77
|
-
printer.debug(`Failed admin app check: ${err.message}`);
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Used to configure the AWS Amplify clients.
|
|
84
|
-
*/
|
|
85
|
-
static async setClientInfo(context: $TSContext, envName?: string, appId?: string): Promise<AmplifyStudioClient> {
|
|
86
|
-
const resolvedEnvName = getEnvName(context, envName);
|
|
87
|
-
const resolvedAppId = getAppId(context, appId);
|
|
88
|
-
const [awsConfigInfo, dataStoreStatus] = await Promise.all([
|
|
89
|
-
context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'loadConfigurationForEnv', [
|
|
90
|
-
context,
|
|
91
|
-
resolvedEnvName,
|
|
92
|
-
resolvedAppId,
|
|
93
|
-
]) as ServiceConfigurationOptions,
|
|
94
|
-
isDataStoreEnabled(context),
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
const client = new AmplifyStudioClient(awsConfigInfo, resolvedAppId, resolvedEnvName);
|
|
98
|
-
|
|
99
|
-
if ((await getTransformerVersion()) === 2) {
|
|
100
|
-
await client.loadMetadata();
|
|
101
|
-
client.isGraphQLSupported = true;
|
|
102
|
-
} else {
|
|
103
|
-
client.isGraphQLSupported = false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
client.isDataStoreEnabled = dataStoreStatus;
|
|
107
|
-
|
|
108
|
-
return client;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/*
|
|
112
|
-
* Builds the clients
|
|
113
|
-
*/
|
|
114
|
-
constructor(awsConfigInfo: ServiceConfigurationOptions, appId: string, envName: string) {
|
|
115
|
-
this.#amplifyUiBuilder = buildAmplifyUiBuilderClient(awsConfigInfo);
|
|
116
|
-
this.#amplifyBackend = buildAmplifyBackendClient(awsConfigInfo);
|
|
117
|
-
this.#appId = appId;
|
|
118
|
-
this.#envName = envName;
|
|
119
|
-
this.metadata = {
|
|
120
|
-
autoGenerateForms: false,
|
|
121
|
-
autoGenerateViews: false,
|
|
122
|
-
formFeatureFlags: {
|
|
123
|
-
isRelationshipSupported: false,
|
|
124
|
-
isNonModelSupported: false,
|
|
125
|
-
},
|
|
126
|
-
isGraphQLEnabled: false,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* loads metadata from appId/envName
|
|
132
|
-
*/
|
|
133
|
-
loadMetadata = async (envName?: string, appId?: string): Promise<void> => {
|
|
134
|
-
const environmentName = envName || this.#envName;
|
|
135
|
-
const resolvedAppId = appId || this.#appId;
|
|
136
|
-
try {
|
|
137
|
-
const response = await this.#amplifyUiBuilder
|
|
138
|
-
.getMetadata({
|
|
139
|
-
appId: resolvedAppId,
|
|
140
|
-
environmentName,
|
|
141
|
-
})
|
|
142
|
-
.promise();
|
|
143
|
-
this.metadata = {
|
|
144
|
-
autoGenerateForms: response.features?.autoGenerateForms === 'true',
|
|
145
|
-
autoGenerateViews: response.features?.autoGenerateViews === 'true',
|
|
146
|
-
formFeatureFlags: {
|
|
147
|
-
isRelationshipSupported: response.features?.isRelationshipSupported === 'true',
|
|
148
|
-
isNonModelSupported: response.features?.isNonModelSupported === 'true',
|
|
149
|
-
},
|
|
150
|
-
isGraphQLEnabled: response.features?.isGraphQLEnabled === 'true',
|
|
151
|
-
};
|
|
152
|
-
} catch (err) {
|
|
153
|
-
throw new Error(`Failed to load metadata: ${err.message}`);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* listComponents from Studio
|
|
159
|
-
*/
|
|
160
|
-
listComponents = async (envName?: string, appId?: string): Promise<{ entities: Component[] }> => {
|
|
161
|
-
const environmentName = envName || this.#envName;
|
|
162
|
-
const resolvedAppId = appId || this.#appId;
|
|
163
|
-
try {
|
|
164
|
-
let nextToken: string | undefined;
|
|
165
|
-
const uiBuilderComponents: Component[] = [];
|
|
166
|
-
do {
|
|
167
|
-
const response = await this.#amplifyUiBuilder
|
|
168
|
-
.exportComponents({
|
|
169
|
-
appId: resolvedAppId,
|
|
170
|
-
environmentName,
|
|
171
|
-
nextToken,
|
|
172
|
-
})
|
|
173
|
-
.promise();
|
|
174
|
-
uiBuilderComponents.push(...response.entities);
|
|
175
|
-
nextToken = response.nextToken;
|
|
176
|
-
} while (nextToken);
|
|
177
|
-
return { entities: uiBuilderComponents };
|
|
178
|
-
} catch (err) {
|
|
179
|
-
throw new Error(`Failed to list components: ${err.message}`);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Returns all the UI Builder themes from the app
|
|
185
|
-
*/
|
|
186
|
-
listThemes = async (envName?: string, appId?: string): Promise<{ entities: Theme[] }> => {
|
|
187
|
-
const environmentName = envName || this.#envName;
|
|
188
|
-
const resolvedAppId = appId || this.#appId;
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
let nextToken: string | undefined;
|
|
192
|
-
const uiBuilderThemes: Theme[] = [];
|
|
193
|
-
do {
|
|
194
|
-
const response = await this.#amplifyUiBuilder
|
|
195
|
-
.exportThemes({
|
|
196
|
-
appId: resolvedAppId,
|
|
197
|
-
environmentName,
|
|
198
|
-
nextToken,
|
|
199
|
-
})
|
|
200
|
-
.promise();
|
|
201
|
-
uiBuilderThemes.push(...response.entities);
|
|
202
|
-
nextToken = response.nextToken;
|
|
203
|
-
} while (nextToken);
|
|
204
|
-
return { entities: uiBuilderThemes };
|
|
205
|
-
} catch (err) {
|
|
206
|
-
throw new Error(`Failed to list themes: ${err.message}`);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
listForms = async (envName?: string, appId?: string): Promise<{ entities: Form[] }> => {
|
|
211
|
-
const environmentName = envName || this.#envName;
|
|
212
|
-
const resolvedAppId = appId || this.#appId;
|
|
213
|
-
try {
|
|
214
|
-
let nextToken: string | undefined;
|
|
215
|
-
const uibuilderForms: Form[] = [];
|
|
216
|
-
do {
|
|
217
|
-
const response = await this.#amplifyUiBuilder
|
|
218
|
-
.exportForms({
|
|
219
|
-
appId: resolvedAppId,
|
|
220
|
-
environmentName,
|
|
221
|
-
nextToken,
|
|
222
|
-
})
|
|
223
|
-
.promise();
|
|
224
|
-
uibuilderForms.push(...response.entities);
|
|
225
|
-
nextToken = response.nextToken;
|
|
226
|
-
} while (nextToken);
|
|
227
|
-
return { entities: uibuilderForms };
|
|
228
|
-
} catch (err) {
|
|
229
|
-
throw new Error(`Failed to list forms: ${err.message}`);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
createComponent = async (component: CreateComponentData, envName?: string, appId?: string): Promise<Component | undefined> => {
|
|
234
|
-
const environmentName = envName || this.#envName;
|
|
235
|
-
const resolvedAppId = appId || this.#appId;
|
|
236
|
-
try {
|
|
237
|
-
const response = await this.#amplifyUiBuilder
|
|
238
|
-
.createComponent({
|
|
239
|
-
appId: resolvedAppId,
|
|
240
|
-
environmentName,
|
|
241
|
-
componentToCreate: component,
|
|
242
|
-
})
|
|
243
|
-
.promise();
|
|
244
|
-
return response.entity;
|
|
245
|
-
} catch (err) {
|
|
246
|
-
throw new Error(`Failed to create component: ${err.message}`);
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
deleteForm = async (formId: string, envName?: string, appId?: string): Promise<void> => {
|
|
251
|
-
const environmentName = envName || this.#envName;
|
|
252
|
-
const resolvedAppId = appId || this.#appId;
|
|
253
|
-
try {
|
|
254
|
-
await this.#amplifyUiBuilder.deleteForm({ id: formId, environmentName, appId: resolvedAppId }).promise();
|
|
255
|
-
} catch (err) {
|
|
256
|
-
printer.debug(err.toString());
|
|
257
|
-
throw err;
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
getModels = async (resourceName: string, envName?: string, appId?: string): Promise<string | undefined> => {
|
|
262
|
-
try {
|
|
263
|
-
const environmentName = envName || this.#envName;
|
|
264
|
-
const resolvedAppId = appId || this.#appId;
|
|
265
|
-
const { Models } = await this.#amplifyBackend
|
|
266
|
-
.getBackendAPIModels({
|
|
267
|
-
AppId: resolvedAppId,
|
|
268
|
-
BackendEnvironmentName: environmentName,
|
|
269
|
-
ResourceName: resourceName,
|
|
270
|
-
})
|
|
271
|
-
.promise();
|
|
272
|
-
return Models;
|
|
273
|
-
} catch (err) {
|
|
274
|
-
throw new Error(`Models not found in AmplifyBackend:GetBackendAPIModels response: ${err.message}`);
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
startCodegenJob = async (codegenJobToCreate: StartCodegenJobData, appId?: string, envName?: string) => {
|
|
279
|
-
const environmentName = envName || this.#envName;
|
|
280
|
-
const resolvedAppId = appId || this.#appId;
|
|
281
|
-
try {
|
|
282
|
-
const response = await this.#amplifyUiBuilder
|
|
283
|
-
.startCodegenJob({
|
|
284
|
-
appId: resolvedAppId,
|
|
285
|
-
environmentName,
|
|
286
|
-
codegenJobToCreate,
|
|
287
|
-
})
|
|
288
|
-
.promise();
|
|
289
|
-
if (!response.entity || !response.entity.id) {
|
|
290
|
-
throw new Error('Error starting codegen job');
|
|
291
|
-
}
|
|
292
|
-
return response.entity.id;
|
|
293
|
-
} catch (err) {
|
|
294
|
-
printer.debug('Failed to start job');
|
|
295
|
-
printer.debug(err.stack);
|
|
296
|
-
throw err;
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
getCodegenJob = async (jobId: string, appId?: string, envName?: string) => {
|
|
301
|
-
const environmentName = envName || this.#envName;
|
|
302
|
-
const resolvedAppId = appId || this.#appId;
|
|
303
|
-
try {
|
|
304
|
-
const { job } = await this.#amplifyUiBuilder
|
|
305
|
-
.getCodegenJob({
|
|
306
|
-
id: jobId,
|
|
307
|
-
appId: resolvedAppId,
|
|
308
|
-
environmentName,
|
|
309
|
-
})
|
|
310
|
-
.promise();
|
|
311
|
-
if (!job) {
|
|
312
|
-
throw new Error('Error getting codegen job');
|
|
313
|
-
}
|
|
314
|
-
return job;
|
|
315
|
-
} catch (err) {
|
|
316
|
-
printer.debug(err.toString());
|
|
317
|
-
throw err;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
}
|
package/src/clients/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as AmplifyStudioClient } from './amplify-studio-client';
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { printer } from '@aws-amplify/amplify-prompts';
|
|
2
|
-
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
3
|
-
import { extractArgs } from './utils';
|
|
4
|
-
import { AmplifyStudioClient } from '../clients';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Clones the components from the source to new environment
|
|
8
|
-
*/
|
|
9
|
-
export const run = async (context: $TSContext): Promise<void> => {
|
|
10
|
-
printer.debug('Running create components command in amplify-util-uibuilder');
|
|
11
|
-
const args = extractArgs(context);
|
|
12
|
-
const sourceEnvName = args.sourceEnvName ? args.sourceEnvName : context.exeInfo.sourceEnvName;
|
|
13
|
-
const newEnvName = args.newEnvName ? args.newEnvName : context.exeInfo.localEnvInfo.envName;
|
|
14
|
-
const appId = args.appId ?? context.exeInfo?.teamProviderInfo?.[sourceEnvName]?.awscloudformation?.AmplifyAppId;
|
|
15
|
-
const studioClient = await AmplifyStudioClient.setClientInfo(context, sourceEnvName, appId);
|
|
16
|
-
|
|
17
|
-
const [existingComponents, existingComponentsNewEnv] = await Promise.all([
|
|
18
|
-
studioClient.listComponents(sourceEnvName),
|
|
19
|
-
studioClient.listComponents(newEnvName),
|
|
20
|
-
]);
|
|
21
|
-
if (existingComponents.entities.length === 0) {
|
|
22
|
-
printer.debug(`${existingComponents.entities.length} components exist in source env. Skipping creation of local components.`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (existingComponentsNewEnv.entities.length > 0) {
|
|
27
|
-
printer.debug(
|
|
28
|
-
`${existingComponentsNewEnv.entities.length} components already exist in new env. Skipping creation of local components.`,
|
|
29
|
-
);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const components = existingComponents.entities;
|
|
34
|
-
if (!components.length) {
|
|
35
|
-
printer.debug(`No UIBuilder components found in app ${appId} from env ${sourceEnvName}. Skipping component clone process.`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
for (let i = 0; i < components.length; i++) {
|
|
39
|
-
const {
|
|
40
|
-
appId: _appId, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
41
|
-
environmentName, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
42
|
-
id, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
43
|
-
createdAt, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
44
|
-
modifiedAt, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
45
|
-
...componentCreateData
|
|
46
|
-
} = components[i];
|
|
47
|
-
await studioClient.createComponent(componentCreateData, newEnvName, appId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
printer.info(
|
|
51
|
-
`Successfully cloned ${components.length} UIBuilder components in app ${appId} from env ${sourceEnvName} to env ${newEnvName}.`,
|
|
52
|
-
);
|
|
53
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/* eslint-disable spellcheck/spell-checker */
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { printer } from '@aws-amplify/amplify-prompts';
|
|
4
|
-
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
5
|
-
import { AmplifyStudioClient } from '../clients';
|
|
6
|
-
import {
|
|
7
|
-
notifyMissingPackages,
|
|
8
|
-
shouldRenderComponents,
|
|
9
|
-
getAmplifyDataSchema,
|
|
10
|
-
isFormDetachedFromModel,
|
|
11
|
-
deleteDetachedForms,
|
|
12
|
-
hasStorageField,
|
|
13
|
-
mapGenericDataSchemaToCodegen,
|
|
14
|
-
waitForSucceededJob,
|
|
15
|
-
extractUIComponents,
|
|
16
|
-
} from './utils';
|
|
17
|
-
import { getUiBuilderComponentsPath } from './utils/getUiBuilderComponentsPath';
|
|
18
|
-
import { AmplifyUIBuilder } from 'aws-sdk';
|
|
19
|
-
import { getApiConfiguration, hasDataStoreConfiguration, hasGraphQLConfiguration } from './utils/getApiConfiguration';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Pulls ui components from Studio backend and generates the code in the user's file system
|
|
23
|
-
*/
|
|
24
|
-
export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull'): Promise<void> => {
|
|
25
|
-
if (!(await shouldRenderComponents(context))) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const spinner = ora('');
|
|
29
|
-
try {
|
|
30
|
-
const studioClient = await AmplifyStudioClient.setClientInfo(context);
|
|
31
|
-
const [componentSchemas, themeSchemas, formSchemas, dataSchema] = await Promise.all([
|
|
32
|
-
studioClient.listComponents(),
|
|
33
|
-
studioClient.listThemes(),
|
|
34
|
-
studioClient.listForms(),
|
|
35
|
-
studioClient.isGraphQLSupported ? getAmplifyDataSchema(context) : Promise.resolve(undefined),
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
const canGenerateDataComponents = dataSchema && studioClient.isGraphQLSupported;
|
|
39
|
-
|
|
40
|
-
const apiConfiguration: AmplifyUIBuilder.ApiConfiguration = canGenerateDataComponents
|
|
41
|
-
? getApiConfiguration(studioClient, context)
|
|
42
|
-
: { noApiConfig: {} };
|
|
43
|
-
const hasDataAPI = hasDataStoreConfiguration(apiConfiguration) || hasGraphQLConfiguration(apiConfiguration);
|
|
44
|
-
const willAutogenerateItems = canGenerateDataComponents && studioClient.metadata.autoGenerateForms && hasDataAPI;
|
|
45
|
-
|
|
46
|
-
if (!willAutogenerateItems && [componentSchemas, themeSchemas, formSchemas].every((group) => !group.entities.length)) {
|
|
47
|
-
printer.debug('Skipping UI component generation since none are found.');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
spinner.start('Generating UI components...');
|
|
51
|
-
|
|
52
|
-
const genericDataSchema = dataSchema ? mapGenericDataSchemaToCodegen(dataSchema) : undefined;
|
|
53
|
-
|
|
54
|
-
const job: AmplifyUIBuilder.StartCodegenJobData = {
|
|
55
|
-
renderConfig: {
|
|
56
|
-
react: {
|
|
57
|
-
module: 'es2020',
|
|
58
|
-
target: 'es2020',
|
|
59
|
-
script: 'jsx',
|
|
60
|
-
renderTypeDeclarations: true,
|
|
61
|
-
apiConfiguration,
|
|
62
|
-
} as AmplifyUIBuilder.ReactStartCodegenJobData,
|
|
63
|
-
},
|
|
64
|
-
autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported && hasDataAPI,
|
|
65
|
-
features: studioClient.metadata.formFeatureFlags,
|
|
66
|
-
};
|
|
67
|
-
// SDK will throw if this is undefined
|
|
68
|
-
// even though it's optional
|
|
69
|
-
if (genericDataSchema) {
|
|
70
|
-
job.genericDataSchema = genericDataSchema;
|
|
71
|
-
}
|
|
72
|
-
const jobId = await studioClient.startCodegenJob(job);
|
|
73
|
-
const finishedJob = await waitForSucceededJob(() => studioClient.getCodegenJob(jobId), { pollInterval: 2000 });
|
|
74
|
-
if (!finishedJob.asset?.downloadUrl) {
|
|
75
|
-
throw new Error('No manifest file in codegen job');
|
|
76
|
-
}
|
|
77
|
-
const uiBuilderComponentsPath = getUiBuilderComponentsPath(context);
|
|
78
|
-
await extractUIComponents(finishedJob.asset.downloadUrl, uiBuilderComponentsPath);
|
|
79
|
-
|
|
80
|
-
const detachedForms: { id: string; name: string }[] = [];
|
|
81
|
-
const failedResponseNames: string[] = [];
|
|
82
|
-
const modelNames = dataSchema?.models ? new Set(Object.keys(dataSchema.models)) : new Set<string>();
|
|
83
|
-
const hasStorageManagerField = formSchemas.entities.some((formSchema) => hasStorageField(formSchema));
|
|
84
|
-
|
|
85
|
-
if (dataSchema && eventType === 'PostPush') {
|
|
86
|
-
formSchemas.entities.forEach((formSchema) => {
|
|
87
|
-
isFormDetachedFromModel(formSchema, modelNames) && detachedForms.push({ id: formSchema.id, name: formSchema.name });
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (finishedJob.statusMessage) {
|
|
92
|
-
const errorStack = JSON.parse(finishedJob.statusMessage)?.codegenErrors as {
|
|
93
|
-
schemaName: string;
|
|
94
|
-
error: string;
|
|
95
|
-
}[];
|
|
96
|
-
errorStack.forEach((e) => failedResponseNames.push(e.schemaName));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (failedResponseNames.length > 0) {
|
|
100
|
-
spinner.fail(`Failed to sync the following components: ${failedResponseNames.join(', ')}`);
|
|
101
|
-
} else {
|
|
102
|
-
spinner.succeed('Synced UI components.');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const invalidComponentNames = [
|
|
106
|
-
...componentSchemas.entities.filter((component) => !component.schemaVersion).map((component) => component.name),
|
|
107
|
-
...formSchemas.entities.filter((form) => !form.schemaVersion).map((form) => form.name),
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
if (invalidComponentNames.length) {
|
|
111
|
-
printer.warn(
|
|
112
|
-
`The components ${invalidComponentNames.join(
|
|
113
|
-
', ',
|
|
114
|
-
)} were synced with an older version of Amplify Studio. Please re-sync your components with Figma to get latest features and changes.`, // eslint-disable-line spellcheck/spell-checker
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
notifyMissingPackages(context, hasStorageManagerField);
|
|
119
|
-
|
|
120
|
-
await deleteDetachedForms(detachedForms, studioClient);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
printer.debug(e);
|
|
123
|
-
spinner.fail('Failed to sync UI components');
|
|
124
|
-
}
|
|
125
|
-
};
|
package/src/commands/help.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { GenericDataSchema, getGenericFromDataStore } from '@aws-amplify/codegen-ui';
|
|
2
|
-
import { printer } from '@aws-amplify/amplify-prompts';
|
|
3
|
-
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
4
|
-
import { ModelIntrospectionSchema } from '@aws-amplify/appsync-modelgen-plugin';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* If models are available, they will be populated in the models field of the returned object.
|
|
8
|
-
* If they're not available, it will return undefined
|
|
9
|
-
*/
|
|
10
|
-
export const getAmplifyDataSchema = async (context: $TSContext): Promise<GenericDataSchema | undefined> => {
|
|
11
|
-
try {
|
|
12
|
-
const localSchema = await context.amplify.invokePluginMethod(context, 'codegen', undefined, 'getModelIntrospection', [context]);
|
|
13
|
-
|
|
14
|
-
if (!localSchema) {
|
|
15
|
-
printer.debug('Local schema not found');
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
return getGenericFromDataStore(localSchema as ModelIntrospectionSchema);
|
|
19
|
-
} catch (e) {
|
|
20
|
-
printer.debug(e.toString());
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Form } from 'aws-sdk/clients/amplifyuibuilder';
|
|
2
|
-
import { AmplifyStudioClient } from '../../clients';
|
|
3
|
-
import { printer } from '@aws-amplify/amplify-prompts';
|
|
4
|
-
import asyncPool from 'tiny-async-pool';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Does the form's reference DataStore type exist in the list of models
|
|
8
|
-
*/
|
|
9
|
-
export const isFormDetachedFromModel = (formSchema: Form, modelNames: Set<string>): boolean => {
|
|
10
|
-
return formSchema.dataType.dataSourceType === 'DataStore' && !modelNames.has(formSchema.dataType.dataTypeName);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A form schema will be customized if fields, style, sectionalElements objects aren't empty
|
|
15
|
-
* @param schema The schema to test
|
|
16
|
-
* @returns boolean
|
|
17
|
-
*/
|
|
18
|
-
export const isFormSchemaCustomized = (formSchema: Form): boolean => {
|
|
19
|
-
const { fields, style, sectionalElements } = formSchema;
|
|
20
|
-
// Fields and sectionalElements just need an empty check
|
|
21
|
-
if (!isEmpty({ ...fields, ...sectionalElements })) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Check style
|
|
26
|
-
// If not empty, at least one style must be defined
|
|
27
|
-
return Object.values(style).some((styleConfig) => styleConfig !== undefined);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Is the schema a StudioForm
|
|
32
|
-
*/
|
|
33
|
-
export const isStudioForm = (schema: Form | undefined): schema is Form => {
|
|
34
|
-
if (!schema) return false;
|
|
35
|
-
return 'formActionType' in schema;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const deleteDetachedForms = async (detachedForms: { id: string; name: string }[], studioClient: AmplifyStudioClient) => {
|
|
39
|
-
const deleteForm = async ({ id, name }: { id: string; name: string }) => {
|
|
40
|
-
try {
|
|
41
|
-
// Try to delete the form but don't stop any other operations if it fails
|
|
42
|
-
await studioClient.deleteForm(id);
|
|
43
|
-
return { status: 'SUCCESS', message: `Deleted detached form ${name}` };
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return { status: 'FAIL', message: `Failed to delete detached form ${name}` };
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
for await (const status of asyncPool(5, detachedForms, deleteForm)) {
|
|
49
|
-
printer.debug(status.message);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
const isEmpty = (obj?: any): boolean => Object.keys(obj).length === 0;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { $TSContext, stateManager } from '@aws-amplify/amplify-cli-core';
|
|
2
|
-
import { extractArgs } from './extractArgs';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Get's the environment name from the given name or the context
|
|
6
|
-
*/
|
|
7
|
-
export const getEnvName = (context: $TSContext, envName?: string): string => {
|
|
8
|
-
const args = extractArgs(context);
|
|
9
|
-
return envName || args?.environmentName || context.exeInfo.localEnvInfo.envName;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Returns the appId from the context
|
|
14
|
-
*/
|
|
15
|
-
export const resolveAppId = (): string | undefined => {
|
|
16
|
-
const meta = stateManager.getMeta();
|
|
17
|
-
return meta?.providers?.awscloudformation?.AmplifyAppId;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns the appId or throws an error if it's not found.
|
|
22
|
-
*/
|
|
23
|
-
export const getAppId = (context: $TSContext, appId?: string): string => {
|
|
24
|
-
const resolvedAppId = appId || extractArgs(context).appId || resolveAppId();
|
|
25
|
-
|
|
26
|
-
if (!resolvedAppId) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
'Unable to sync Studio components since appId could not be determined. This can happen when you hit the soft limit of number of apps that you can have in Amplify console.',
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return resolvedAppId;
|
|
32
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { $TSContext } from '@aws-amplify/amplify-cli-core';
|
|
2
|
-
|
|
3
|
-
export const extractArgs = (context: $TSContext) => {
|
|
4
|
-
if (!context.input.options) {
|
|
5
|
-
context.input.options = {};
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const environmentName = context.input.options.envName || null;
|
|
9
|
-
const appId = context.input.options.appId || null;
|
|
10
|
-
const amplifyDir = context.input.options.amplifyDir || null;
|
|
11
|
-
const srcDir = context.input.options.srcDir || null;
|
|
12
|
-
const localEnvFilePath = context.input.options.localEnvFilePath || null;
|
|
13
|
-
const sourceEnvName = context.input.options.sourceEnvName || null;
|
|
14
|
-
const newEnvName = context.input.options.newEnvName || null;
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
environmentName,
|
|
18
|
-
appId,
|
|
19
|
-
amplifyDir,
|
|
20
|
-
srcDir,
|
|
21
|
-
localEnvFilePath,
|
|
22
|
-
sourceEnvName,
|
|
23
|
-
newEnvName,
|
|
24
|
-
};
|
|
25
|
-
};
|