From b706af2f1e33f5566ffbff790f58c8b9932023e4 Mon Sep 17 00:00:00 2001 From: Siddharth Gupta Date: Sun, 31 May 2026 18:45:00 -0700 Subject: [PATCH] fix(cli): clear error for intrinsic certificateArn in domain commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `appsync domain` CLI commands call the AppSync SDK directly, which needs a literal certificate ARN string. Passing a CloudFormation intrinsic (e.g. Fn::ImportValue) — valid in the CloudFormation path — previously failed with the cryptic SDK error 'Expected params.certificateArn to be a string'. Throw an actionable error from createDomain() when certificateArn is not a string, pointing users to either a literal ARN or the CloudFormation integration (where the intrinsic resolves). Widen DomainConfig. certificateArn to string | IntrinsicFunction to match validation, add a negative test, and note the limitation in the custom-domain docs. Closes #532 --- doc/custom-domain.md | 2 ++ src/__tests__/commands.test.ts | 26 ++++++++++++++++++++++++++ src/index.ts | 8 ++++++++ src/types/common.ts | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/custom-domain.md b/doc/custom-domain.md index c376b8a9..2b0e863a 100644 --- a/doc/custom-domain.md +++ b/doc/custom-domain.md @@ -51,6 +51,8 @@ For example, if you want to use blue/green deployments, you might need to associ For more information about managing domains with the CLI, see the [Commands](commands.md#domain) section. +⚠️ The CLI commands resolve values themselves rather than through CloudFormation, so `certificateArn` must be a literal ARN string when using them. CloudFormation intrinsic functions (e.g. `Fn::ImportValue`) are only resolved when the domain is managed by CloudFormation (`useCloudFormation: true`). + ## Ejecting from CloudFormation If you started to manage your domain through CloudFormation and want to eject from it, follow the following steps: diff --git a/src/__tests__/commands.test.ts b/src/__tests__/commands.test.ts index 099876dd..560da647 100644 --- a/src/__tests__/commands.test.ts +++ b/src/__tests__/commands.test.ts @@ -93,6 +93,32 @@ describe('create domain', () => { `); }); + it('should reject an intrinsic-function certificateArn (CLI cannot resolve it)', async () => { + mockSend.mockResolvedValue({}); + + await expect( + runServerless({ + fixture: 'appsync', + command: 'appsync domain create', + configExt: { + appSync: { + domain: { + useCloudFormation: false, + certificateArn: { 'Fn::ImportValue': 'exportedCertArn' }, + }, + }, + }, + }), + ).rejects.toThrow( + /the `appsync domain` CLI commands require a plain ARN string/, + ); + + const createCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof CreateDomainNameCommand, + ); + expect(createCall).toBeUndefined(); + }); + it('should create a domain and find a matching certificate, exact match', async () => { mockSend.mockImplementation((cmd) => { if (cmd instanceof ListCertificatesCommand) { diff --git a/src/index.ts b/src/index.ts index f315d889..51aea2ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -715,6 +715,14 @@ class ServerlessAppsyncPlugin { ); } + if (typeof certificateArn !== 'string') { + throw new this.serverless.classes.Error( + `Invalid \`certificateArn\`: the \`appsync domain\` CLI commands require a plain ARN string. ` + + `CloudFormation intrinsic functions (e.g. Fn::ImportValue) can only be resolved by CloudFormation, not by the CLI. ` + + `Either pass a literal ARN, or manage the domain through CloudFormation (the default, \`domain.useCloudFormation: true\`), where the intrinsic function will be resolved.`, + ); + } + await this.clientFactory.getAppSyncClient().send( new CreateDomainNameCommand({ domainName: domain.name, diff --git a/src/types/common.ts b/src/types/common.ts index b5abedbf..58da4e10 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -119,7 +119,7 @@ export type DomainConfig = { useCloudFormation?: boolean; retain?: boolean; name: string; - certificateArn?: string; + certificateArn?: string | IntrinsicFunction; hostedZoneId?: string; hostedZoneName?: string; route53?: boolean;