diff --git a/packages/amplify-e2e-core/src/init/amplifyPush.ts b/packages/amplify-e2e-core/src/init/amplifyPush.ts index bf359bb3750..2228ce3de2e 100644 --- a/packages/amplify-e2e-core/src/init/amplifyPush.ts +++ b/packages/amplify-e2e-core/src/init/amplifyPush.ts @@ -98,9 +98,18 @@ export function amplifyPushWithoutCodegen(cwd: string, testingWithLatestCodebase }); } -export function amplifyPushUpdate(cwd: string, waitForText?: RegExp, testingWithLatestCodebase: boolean = false): Promise { +export function amplifyPushUpdate( + cwd: string, + waitForText?: RegExp, + testingWithLatestCodebase: boolean = false, + allowDestructiveUpdates: boolean = false, +): Promise { + const args = ['push']; + if (allowDestructiveUpdates) { + args.push('--allow-destructuve-graphql-schema-updates'); + } return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + spawn(getCLIPath(testingWithLatestCodebase), args, { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('Are you sure you want to continue?') .sendConfirmYes() .wait(waitForText || /.*/) @@ -130,9 +139,17 @@ export function amplifyPushAuth(cwd: string, testingWithLatestCodebase: boolean }); } -export function amplifyPushUpdateForDependentModel(cwd: string, testingWithLatestCodebase: boolean = false): Promise { +export function amplifyPushUpdateForDependentModel( + cwd: string, + testingWithLatestCodebase: boolean = false, + allowDestructiveUpdates: boolean = false, +): Promise { + const args = ['push']; + if (allowDestructiveUpdates) { + args.push('--allow-destructive-graphql-schema-updates'); + } return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + spawn(getCLIPath(testingWithLatestCodebase), args, { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('Are you sure you want to continue?') .sendConfirmYes() .wait(/.*/) @@ -251,7 +268,7 @@ export function amplifyPushWithNoChanges(cwd: string, testingWithLatestCodebase: return new Promise((resolve, reject) => { spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('No changes detected') - .run((err: Error) => err ? reject(err) : resolve()); + .run((err: Error) => (err ? reject(err) : resolve())); }); } diff --git a/packages/amplify-e2e-core/src/utils/headless.ts b/packages/amplify-e2e-core/src/utils/headless.ts index 2fbce197d92..a1980573f21 100644 --- a/packages/amplify-e2e-core/src/utils/headless.ts +++ b/packages/amplify-e2e-core/src/utils/headless.ts @@ -6,8 +6,8 @@ export const addHeadlessApi = async (cwd: string, request: AddApiRequest) => { await executeHeadlessCommand(cwd, 'api', 'add', request); }; -export const updateHeadlessApi = async (cwd: string, request: UpdateApiRequest) => { - await executeHeadlessCommand(cwd, 'api', 'update', request); +export const updateHeadlessApi = async (cwd: string, request: UpdateApiRequest, allowDestructiveUpdates?: boolean) => { + await executeHeadlessCommand(cwd, 'api', 'update', request, allowDestructiveUpdates); }; export const removeHeadlessApi = async (cwd: string, apiName: string) => { @@ -33,8 +33,18 @@ export const headlessAuthImport = async (cwd: string, request: ImportAuthRequest const headlessRemoveResource = async (cwd: string, category: string, resourceName: string) => { await execa(getCLIPath(), ['remove', category, resourceName, '--yes'], { cwd }); }; -const executeHeadlessCommand = async (cwd: string, category: string, operation: string, request: AnyHeadlessRequest) => { - await execa(getCLIPath(), [operation, category, '--headless'], { input: JSON.stringify(request), cwd }); +const executeHeadlessCommand = async ( + cwd: string, + category: string, + operation: string, + request: AnyHeadlessRequest, + allowDestructiveUpdates: boolean = false, +) => { + const args = [operation, category, '--headless']; + if (allowDestructiveUpdates) { + args.push('--allow-destructuve-graphql-schema-updates'); + } + await execa(getCLIPath(), args, { input: JSON.stringify(request), cwd }); }; type AnyHeadlessRequest = AddApiRequest | UpdateApiRequest | AddAuthRequest | UpdateAuthRequest | ImportAuthRequest; diff --git a/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts index e20b226cab2..b03f2ce2110 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts @@ -235,7 +235,7 @@ describe('amplify add api (GraphQL)', () => { ); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema); - await amplifyPushUpdateForDependentModel(projRoot); + await amplifyPushUpdateForDependentModel(projRoot, undefined, true); const meta = getProjectMeta(projRoot); const region = meta.providers.awscloudformation.Region; const { output } = meta.api.blogapp; diff --git a/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts index 266fafd3423..1f6630c26f4 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts @@ -123,8 +123,8 @@ describe('amplify add api (GraphQL)', () => { await initJSProjectWithProfile(projRoot, {}); await addHeadlessApi(projRoot, addApiRequest); await amplifyPush(projRoot); - await updateHeadlessApi(projRoot, updateApiRequest); - await amplifyPushUpdate(projRoot); + await updateHeadlessApi(projRoot, updateApiRequest, true); + await amplifyPushUpdate(projRoot, undefined, undefined, true); // verify const meta = getProjectMeta(projRoot); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts index 2c4109c2b8b..b76e34a6b72 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts @@ -33,6 +33,6 @@ describe('Schema iterative update - rename @key', () => { const finalSchema = path.join('iterative-push', 'change-model-name', 'final-schema.graphql'); await updateApiSchema(projectDir, apiName, finalSchema); - await amplifyPushUpdate(projectDir); + await amplifyPushUpdate(projectDir, undefined, undefined, true); }); }); diff --git a/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts index 6fdae96a7f9..40f83217ef5 100644 --- a/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts +++ b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts @@ -31,7 +31,7 @@ export const prependDeploymentStepsToDisconnectFunctionsFromReplacedModelTables ): Promise => { const amplifyMeta = stateManager.getMeta(); const rootStackId = amplifyMeta?.providers?.awscloudformation?.StackId; - const allFunctionNames = Object.keys(amplifyMeta?.function); + const allFunctionNames = Object.keys(amplifyMeta?.function || {}); functionsDependentOnReplacedModelTables = await getDependentFunctions( modelsBeingReplaced, allFunctionNames, diff --git a/packages/amplify-provider-awscloudformation/src/push-resources.ts b/packages/amplify-provider-awscloudformation/src/push-resources.ts index e738daccc94..23a1331f0f7 100644 --- a/packages/amplify-provider-awscloudformation/src/push-resources.ts +++ b/packages/amplify-provider-awscloudformation/src/push-resources.ts @@ -160,7 +160,6 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject, re } let deploymentSteps: DeploymentStep[] = []; - let functionsDependentOnReplacedModelTables: string[] = []; // location where the intermediate deployment steps are stored let stateFolder: { local?: string; cloud?: string } = {}; diff --git a/packages/graphql-auth-transformer/src/resources.ts b/packages/graphql-auth-transformer/src/resources.ts index 6749af89196..f81640e94c2 100644 --- a/packages/graphql-auth-transformer/src/resources.ts +++ b/packages/graphql-auth-transformer/src/resources.ts @@ -109,9 +109,11 @@ export class ResourceFactory { expirationDays = apiKeyConfig.apiKeyExpirationDays; } // add delay expiration time is valid upon resource creation - let expirationDateInSeconds = 60 /* s */ * 60 /* m */ * 24 /* h */ * expirationDays; /* d */ - // Add a 2 minute time delay if set to 1 day: https://github.com/aws-amplify/amplify-cli/issues/4460 - if (expirationDays === 1) expirationDateInSeconds += 60 * 2; + let expirationDateInSeconds = 60 * 60 * 24 * expirationDays; // sec * min * hour * days + // Add a 30 minute time delay if set to 1 day: https://github.com/aws-amplify/amplify-cli/issues/4460 + // initially this was 2 minutes but with iterative deployments it is possible for deployments to take much longer than that + // if an iterative deployment takes longer than 30 mins, and API key expiration is set to 1 day, deployments may still fail + if (expirationDays === 1) expirationDateInSeconds += 60 * 30; const nowEpochTime = Math.floor(Date.now() / 1000); return new AppSync.ApiKey({ ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), diff --git a/packages/graphql-transformer-core/src/util/sanity-check.ts b/packages/graphql-transformer-core/src/util/sanity-check.ts index 1e2312c00ea..9de3dcadc9c 100644 --- a/packages/graphql-transformer-core/src/util/sanity-check.ts +++ b/packages/graphql-transformer-core/src/util/sanity-check.ts @@ -415,7 +415,9 @@ export const cantRemoveTableAfterCreation = (_: Diff, currentBuild: DiffableProj .map(([name]) => name); const currentModels = getNestedStackLogicalIds(currentBuild); const nextModels = getNestedStackLogicalIds(nextBuild); - const removedModels = currentModels.filter(currModel => !nextModels.includes(currModel)); + const removedModels = currentModels + .filter(currModel => !nextModels.includes(currModel)) + .filter(stackLogicalId => stackLogicalId !== 'ConnectionStack'); if (removedModels.length > 0) { throw new DestructiveMigrationError( 'Removing a model from the GraphQL schema will also remove the underlying DynamoDB table.',