writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
website-infraDeploys AWS CloudFormation infrastructure stacks for the website (S3, CloudFront, Route 53, ACM, and optionally contact form API).
Independent GitHub snapshot · Automated classification · Original documentation governs
You are running the /grc-portfolio:infra skill. Your job is to deploy the AWS infrastructure needed to host the website using CloudFormation.
Find site-config.json:
$ARGUMENTS for a project directory pathRead it and validate that status.preflightComplete === true. If not, tell the user to run /grc-portfolio:preflight first.
Based on features.customDomain:
$TOOLKIT_DIR/cloudformation/website-infrastructure.yaml$TOOLKIT_DIR/cloudformation/website-infrastructure-no-domain.yamlRun the validation script:
$TOOLKIT_DIR/scripts/validate-stack.sh <template-path> <aws.profile>
If validation fails, report the error and stop.
Use aws cloudformation deploy — it is idempotent (creates the stack on
first run, applies a change-set on subsequent runs) and waits for completion
inline, so the user doesn't need a separate wait step.
With custom domain:
aws cloudformation deploy \
--stack-name <aws.stackName> \
--template-file <template-path> \
--parameter-overrides \
ProjectName=<projectName> \
DomainName=<aws.domain> \
HostedZoneId=<aws.hostedZoneId> \
CertificateArn=<aws.certArn> \
--profile <aws.profile> \
--region us-east-1 \
--no-fail-on-empty-changeset
Without custom domain:
aws cloudformation deploy \
--stack-name <aws.stackName> \
--template-file <template-path> \
--parameter-overrides ProjectName=<projectName> \
--profile <aws.profile> \
--region us-east-1 \
--no-fail-on-empty-changeset
Note: Check the actual template parameters first by reading the template file, as parameter names may differ from the examples above. Use the actual parameter names from the template.
The deploy command can take 5-15 minutes (especially with CloudFront distribution creation). Tell the user it's in progress and approximately how long it might take.
If the deploy fails, retrieve the failure reason:
aws cloudformation describe-stack-events \
--stack-name <aws.stackName> \
--profile <aws.profile> \
--region us-east-1 \
--query "StackEvents[?ResourceStatus=='CREATE_FAILED' || ResourceStatus=='UPDATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \
--output table
aws cloudformation describe-stacks \
--stack-name <aws.stackName> \
--profile <aws.profile> \
--region us-east-1 \
--query "Stacks[0].Outputs"
Extract and save to site-config.json:
aws.bucketName -- the S3 bucket nameaws.distributionId -- the CloudFront distribution IDaws.cloudFrontUrl -- the CloudFront domain name (e.g., d123abc.cloudfront.net)If features.contactForm is true:
Ask the user for two SES-verified email addresses (no defaults — the stack will refuse to deploy without them):
SESFromEmail: verified sender identitySESToEmail: recipient inbox for submissionsIf either is not yet verified in SES, stop and instruct the user to verify
them in the AWS Console (SES → Verified identities → Create identity)
before continuing. Save both to aws.sesFromEmail and aws.sesToEmail
in site-config.json.
Deploy the contact form stack:
aws cloudformation deploy \
--template-file $TOOLKIT_DIR/cloudformation/contact-form-api.yaml \
--stack-name <projectName>-contact-form-api \
--parameter-overrides "SESFromEmail=<aws.sesFromEmail>" "SESToEmail=<aws.sesToEmail>" \
--capabilities CAPABILITY_IAM \
--profile <aws.profile> \
--region us-east-1 \
--no-fail-on-empty-changeset
aws cloudformation describe-stacks \
--stack-name <projectName>-contact-form-api \
--profile <aws.profile> \
--region us-east-1 \
--query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \
--output text
Save to aws.contactApiEndpoint in config.
Package and deploy the Lambda function code (reference $TOOLKIT_DIR/scripts/deploy-contact-api.sh for the pattern -- create temp dir, copy handler, install @aws-sdk/client-ses, zip, update function code). The function name is <projectName>-contact-form-api-handler (derived from the stack name in the template).
If using a custom domain, tell the user:
dig NS <domain>Get the nameservers:
aws route53 get-hosted-zone --id <aws.hostedZoneId> --profile <aws.profile> --query "DelegationSet.NameServers"
Update site-config.json:
aws.bucketName, aws.distributionId, aws.cloudFrontUrlaws.contactApiEndpoint (if contact form)status.infraDeployed = trueTell the user:
/grc-portfolio:deploy next to push the built site to AWS$TOOLKIT_DIR = read from site-config.json toolkitDir field$ARGUMENTS = arguments passed after /infra (expected: project directory path)