scf-deploy
Version:
S3 + CloudFront static deployment automation CLI
894 lines (858 loc) • 26.5 kB
TypeScript
import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { S3Client } from '@aws-sdk/client-s3';
import { CloudFrontClient, Distribution, Invalidation } from '@aws-sdk/client-cloudfront';
import { STSClient } from '@aws-sdk/client-sts';
/**
* Configuration types for SCF
*/
/**
* Environment-specific configuration override
* Allows partial overrides of nested configurations
*/
interface EnvironmentConfig {
/** Application name override */
app?: string;
/** AWS region override */
region?: string;
/** AWS credentials override */
credentials?: Partial<AWSCredentialsConfig>;
/** S3 bucket configuration override */
s3?: Partial<S3Config>;
/** CloudFront distribution configuration override */
cloudfront?: Partial<CloudFrontConfig>;
}
/**
* Main SCF configuration interface
*/
interface SCFConfig {
/** Application name (used for resource naming) */
app: string;
/** AWS region */
region: string;
/** AWS credentials configuration */
credentials?: AWSCredentialsConfig;
/** S3 bucket configuration */
s3?: S3Config;
/** CloudFront distribution configuration */
cloudfront?: CloudFrontConfig;
/** Environment-specific configurations */
environments?: Record<string, EnvironmentConfig>;
}
/**
* AWS credentials configuration
*/
interface AWSCredentialsConfig {
/** AWS profile name from ~/.aws/credentials */
profile?: string;
/** AWS access key ID */
accessKeyId?: string;
/** AWS secret access key */
secretAccessKey?: string;
/** AWS session token (for temporary credentials) */
sessionToken?: string;
}
/**
* S3 bucket configuration
*/
interface S3Config {
/** S3 bucket name */
bucketName: string;
/**
* Build directory to upload (optional, auto-detected if not provided)
*
* If not specified, scf will automatically detect common build directories:
* - dist (Vite, Rollup)
* - build (Create React App, Next.js)
* - out (Next.js static export)
* - .next (Next.js production)
* - public (Static sites)
* - And more...
*/
buildDir?: string;
/** Index document for static website hosting */
indexDocument?: string;
/** Error document for static website hosting */
errorDocument?: string;
/** Whether to enable static website hosting */
websiteHosting?: boolean;
/** Maximum concurrent uploads */
concurrency?: number;
/** Whether to enable Gzip compression */
gzip?: boolean;
/** File patterns to exclude from upload */
exclude?: string[];
}
/**
* CloudFront distribution configuration
*/
interface CloudFrontConfig {
/** Whether to enable CloudFront */
enabled: boolean;
/** Distribution price class */
priceClass?: 'PriceClass_100' | 'PriceClass_200' | 'PriceClass_All';
/** Custom domain configuration */
customDomain?: {
/** Domain name (e.g., example.com) */
domainName: string;
/** ACM certificate ARN */
certificateArn: string;
/** Alternative domain names (CNAMEs) */
aliases?: string[];
};
/** Default TTL in seconds */
defaultTTL?: number;
/** Max TTL in seconds */
maxTTL?: number;
/** Min TTL in seconds */
minTTL?: number;
/** Whether to enable IPv6 */
ipv6?: boolean;
/** Custom error responses */
errorPages?: Array<{
errorCode: number;
responseCode?: number;
responsePath?: string;
cacheTTL?: number;
}>;
/** Cache warming configuration (warm up CloudFront edge locations after deployment) */
cacheWarming?: {
/** Enable cache warming */
enabled: boolean;
/** Paths to warm up (default: ['/']) */
paths?: string[];
/** Number of concurrent requests (default: 3, max: 10) */
concurrency?: number;
/** Delay between requests in ms (default: 500ms, min: 100ms) */
delay?: number;
};
}
/**
* Load config options
*/
interface LoadConfigOptions {
/** Config file path (default: auto-discover) */
configPath?: string;
/** Environment name (e.g., 'dev', 'prod') */
env?: string;
/** AWS profile override */
profile?: string;
}
/**
* AWS-related type definitions
*/
/**
* AWS credentials (from AWS SDK)
*/
type AWSCredentials = AwsCredentialIdentity;
/**
* AWS account information from STS
*/
interface AWSAccountInfo {
/** AWS Account ID */
accountId: string;
/** User ARN */
arn: string;
/** User ID */
userId: string;
}
/**
* Credential source type
*/
type CredentialSource = "config" | "environment" | "profile" | "instance-metadata" | "default-chain";
/**
* Credential resolution result
*/
interface CredentialResolution {
/** Resolved credentials */
credentials: AWSCredentials;
/** Source of credentials */
source: CredentialSource;
/** Profile name (if using profile) */
profile?: string;
}
/**
* Deployer-related type definitions
*/
/**
* File information for deployment
*/
interface FileInfo {
/** Absolute path to file */
absolutePath: string;
/** Relative path from build directory */
relativePath: string;
/** S3 key (path in bucket) */
key: string;
/** File size in bytes */
size: number;
/** File hash (SHA-256) */
hash: string;
/** Content-Type (MIME type) */
contentType: string;
/** Whether file should be gzipped */
shouldGzip: boolean;
}
/**
* Upload result for a single file
*/
interface UploadResult {
/** File info */
file: FileInfo;
/** Whether upload was successful */
success: boolean;
/** Upload status */
status: "uploaded" | "skipped" | "failed";
/** Error message if failed */
error?: string;
/** Upload duration in milliseconds */
duration?: number;
}
/**
* Deployment statistics
*/
interface DeploymentStats {
/** Total files scanned */
totalFiles: number;
/** Files uploaded */
uploaded: number;
/** Files skipped (unchanged) */
skipped: number;
/** Files failed */
failed: number;
/** Total bytes uploaded */
totalSize: number;
/** Total bytes uploaded (after compression) */
compressedSize: number;
/** Deployment duration in milliseconds */
duration: number;
/** Upload results */
results: UploadResult[];
}
/**
* Upload options
*/
interface UploadOptions {
/** Whether to enable gzip compression */
gzip?: boolean;
/** Maximum concurrent uploads */
concurrency?: number;
/** Whether to show progress */
showProgress?: boolean;
/** Whether to perform dry-run (no actual upload) */
dryRun?: boolean;
/** Environment name for state management */
environment?: string;
/** Whether to use incremental deployment (default: true) */
useIncrementalDeploy?: boolean;
/** Force full deployment (ignore state, upload all files) */
forceFullDeploy?: boolean;
/** Whether to save state after deployment (default: true) */
saveState?: boolean;
}
/**
* File scan options
*/
interface ScanOptions {
/** Build directory to scan */
buildDir: string;
/** File patterns to exclude */
exclude?: string[];
/** Whether to follow symlinks */
followSymlinks?: boolean;
}
/**
* State management types
*/
/**
* S3 resource state
*/
interface S3ResourceState {
bucketName: string;
region: string;
websiteUrl?: string;
}
/**
* CloudFront resource state
*/
interface CloudFrontResourceState {
distributionId: string;
domainName: string;
distributionUrl: string;
aliases?: string[];
}
/**
* AWS resources state
*/
interface ResourcesState {
s3?: S3ResourceState;
cloudfront?: CloudFrontResourceState;
}
/**
* File hash map (path -> hash)
*/
interface FileHashMap {
[]: string;
}
/**
* Deployment state
*/
interface DeploymentState {
app: string;
environment: string;
lastDeployed: string;
resources: ResourcesState;
files: FileHashMap;
version?: string;
}
/**
* File change detection result
*/
interface FileChange {
path: string;
hash: string;
status: "added" | "modified" | "unchanged" | "deleted";
previousHash?: string;
}
/**
* File changes summary
*/
interface FileChanges {
added: FileChange[];
modified: FileChange[];
unchanged: FileChange[];
deleted: FileChange[];
totalChanges: number;
}
/**
* State file options
*/
interface StateOptions {
stateDir?: string;
environment?: string;
}
/**
* State manager options
*/
interface StateManagerOptions {
stateDir?: string;
createIfMissing?: boolean;
}
/**
* Config utility functions
*/
/**
* Helper function for defining config with type safety
* This is exported for user config files
*/
declare function defineConfig(config: SCFConfig): SCFConfig;
/**
* Generate example config file content
*/
declare function generateExampleConfig(appName?: string): string;
/**
* Config system entry point
*/
/**
* Load and validate SCF configuration
*
* @param options - Load config options
* @returns Validated and merged configuration
*
* @example
* ```ts
* // Load default config
* const config = await loadConfig();
*
* // Load with environment
* const config = await loadConfig({ env: 'prod' });
*
* // Load with custom path
* const config = await loadConfig({ configPath: './custom.config.ts' });
* ```
*/
declare function loadConfig(options?: LoadConfigOptions): Promise<SCFConfig>;
/**
* AWS Credentials resolution
*/
/**
* Get AWS credentials from config with priority order:
* 1. Explicit credentials in config (accessKeyId + secretAccessKey)
* 2. Profile from config
* 3. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
* 4. Default credential provider chain (profile → IAM role)
*/
declare function getCredentials(config: SCFConfig): Promise<CredentialResolution>;
/**
* Create a credential provider for AWS SDK clients
*/
declare function createCredentialProvider(config: SCFConfig): AwsCredentialIdentityProvider;
/**
* AWS Credentials verification using STS
*/
/**
* Verify AWS credentials by calling STS GetCallerIdentity
*
* @param credentials - AWS credentials to verify
* @param region - AWS region
* @returns AWS account information
* @throws Error if credentials are invalid
*/
declare function verifyCredentials(credentials: AWSCredentials, region: string): Promise<AWSAccountInfo>;
/**
* Format AWS account info for display
*/
declare function formatAccountInfo(info: AWSAccountInfo): string;
/**
* AWS Client creation helpers
*/
/**
* Create S3 client with credentials from config
*/
declare function createS3Client(config: SCFConfig): S3Client;
/**
* Create CloudFront client with credentials from config
* Note: CloudFront is always in us-east-1 for API calls
*/
declare function createCloudFrontClient(config: SCFConfig): CloudFrontClient;
/**
* Create STS client with credentials from config
*/
declare function createSTSClient(config: SCFConfig): STSClient;
/**
* Client configuration options
*/
interface ClientOptions {
/** AWS region override */
region?: string;
/** Request timeout in milliseconds */
requestTimeout?: number;
/** Max retry attempts */
maxAttempts?: number;
}
/**
* Create S3 client with custom options
*/
declare function createS3ClientWithOptions(config: SCFConfig, options?: ClientOptions): S3Client;
/**
* Create CloudFront client with custom options
*/
declare function createCloudFrontClientWithOptions(config: SCFConfig, options?: ClientOptions): CloudFrontClient;
/**
* S3 Bucket management
*/
/**
* Check if bucket exists
*/
declare function bucketExists(client: S3Client, bucketName: string): Promise<boolean>;
/**
* Create S3 bucket
*/
declare function createBucket(client: S3Client, bucketName: string, region: string): Promise<void>;
/**
* Configure bucket for static website hosting
*/
declare function configureBucketWebsite(client: S3Client, bucketName: string, indexDocument?: string, errorDocument?: string): Promise<void>;
/**
* Set bucket policy for public read access
*/
declare function setBucketPublicReadPolicy(client: S3Client, bucketName: string): Promise<void>;
/**
* Ensure bucket exists and is properly configured
*/
declare function ensureBucket(client: S3Client, bucketName: string, region: string, options?: {
websiteHosting?: boolean;
indexDocument?: string;
errorDocument?: string;
publicRead?: boolean;
}): Promise<void>;
/**
* Get bucket website URL
*/
declare function getBucketWebsiteUrl(bucketName: string, region: string): string;
/**
* S3 Deployer - Main deployment orchestrator
*/
/**
* Deploy to S3
*/
declare function deployToS3(config: SCFConfig, options?: UploadOptions): Promise<DeploymentStats>;
/**
* CloudFront Distribution management
*/
/**
* Distribution creation options
*/
interface CreateDistributionOptions {
s3BucketName: string;
s3Region: string;
indexDocument?: string;
customDomain?: {
domainName: string;
certificateArn: string;
aliases?: string[];
};
priceClass?: 'PriceClass_100' | 'PriceClass_200' | 'PriceClass_All';
defaultTTL?: number;
maxTTL?: number;
minTTL?: number;
ipv6?: boolean;
}
/**
* Check if distribution exists
*/
declare function distributionExists(client: CloudFrontClient, distributionId: string): Promise<boolean>;
/**
* Get distribution details
*/
declare function getDistribution(client: CloudFrontClient, distributionId: string): Promise<Distribution | null>;
/**
* Create CloudFront distribution
*/
declare function createDistribution(client: CloudFrontClient, options: CreateDistributionOptions): Promise<Distribution>;
/**
* Update existing distribution
*/
declare function updateDistribution(client: CloudFrontClient, distributionId: string, updates: Partial<CreateDistributionOptions>): Promise<Distribution>;
/**
* Wait for distribution to be deployed
*/
declare function waitForDistributionDeployed(client: CloudFrontClient, distributionId: string, options?: {
maxWaitTime?: number;
minDelay?: number;
maxDelay?: number;
}): Promise<void>;
/**
* Get distribution domain name
*/
declare function getDistributionDomainName(distribution: Distribution): string;
/**
* Get distribution URL
*/
declare function getDistributionUrl(distribution: Distribution): string;
/**
* CloudFront cache invalidation
*/
/**
* Invalidation options
*/
interface InvalidationOptions {
paths: string[];
callerReference?: string;
}
/**
* Create cache invalidation
*/
declare function createInvalidation(client: CloudFrontClient, distributionId: string, options: InvalidationOptions): Promise<Invalidation>;
/**
* Get invalidation status
*/
declare function getInvalidation(client: CloudFrontClient, distributionId: string, invalidationId: string): Promise<Invalidation | null>;
/**
* Wait for invalidation to complete
*/
declare function waitForInvalidationCompleted(client: CloudFrontClient, distributionId: string, invalidationId: string, options?: {
maxWaitTime?: number;
minDelay?: number;
maxDelay?: number;
}): Promise<void>;
/**
* Create and wait for invalidation to complete
*/
declare function invalidateCache(client: CloudFrontClient, distributionId: string, paths: string[], options?: {
wait?: boolean;
maxWaitTime?: number;
minDelay?: number;
maxDelay?: number;
}): Promise<Invalidation>;
/**
* Invalidate all files (/* wildcard)
*/
declare function invalidateAll(client: CloudFrontClient, distributionId: string, options?: {
wait?: boolean;
maxWaitTime?: number;
}): Promise<Invalidation>;
/**
* Check if invalidation is complete
*/
declare function isInvalidationComplete(invalidation: Invalidation): boolean;
/**
* Get invalidation progress
*/
declare function getInvalidationStatus(invalidation: Invalidation): string;
/**
* CloudFront Deployer - Main deployment orchestrator
*/
/**
* CloudFront deployment options
*/
interface CloudFrontDeploymentOptions {
distributionId?: string;
invalidatePaths?: string[];
invalidateAll?: boolean;
waitForDeployment?: boolean;
waitForInvalidation?: boolean;
showProgress?: boolean;
environment?: string;
saveState?: boolean;
}
/**
* CloudFront deployment result
*/
interface CloudFrontDeploymentResult {
distributionId: string;
distributionDomain: string;
distributionUrl: string;
invalidationId?: string;
isNewDistribution: boolean;
deploymentTime: number;
}
/**
* Deploy to CloudFront
*/
declare function deployToCloudFront(config: SCFConfig, s3DeploymentStats: DeploymentStats, options?: CloudFrontDeploymentOptions): Promise<CloudFrontDeploymentResult>;
/**
* Combined S3 + CloudFront deployment
*/
declare function deployWithCloudFront(config: SCFConfig, deployToS3: (config: SCFConfig, options?: UploadOptions) => Promise<DeploymentStats>, options?: {
s3Options?: UploadOptions;
cloudFrontOptions?: CloudFrontDeploymentOptions;
}): Promise<{
s3Stats: DeploymentStats;
cloudFront: CloudFrontDeploymentResult;
}>;
/**
* File scanner for deployment
*/
/**
* Scan files in build directory
*/
declare function scanFiles(options: ScanOptions): Promise<FileInfo[]>;
/**
* Calculate hash for a single file
*/
declare function calculateFileHash(filePath: string): Promise<string>;
/**
* Filter files by hash comparison
* Returns only files that have changed (different hash)
*/
declare function filterChangedFiles(files: FileInfo[], existingHashes: Record<string, string>): FileInfo[];
/**
* Group files by whether they should be gzipped
*/
declare function groupFilesByCompression(files: FileInfo[]): {
gzipped: FileInfo[];
plain: FileInfo[];
};
/**
* S3 file uploader
*/
/**
* Upload a single file to S3
*/
declare function uploadFile(client: S3Client, bucketName: string, file: FileInfo, options?: UploadOptions): Promise<UploadResult>;
/**
* Upload multiple files in parallel
*/
declare function uploadFiles(client: S3Client, bucketName: string, files: FileInfo[], options?: UploadOptions, onProgress?: (completed: number, total: number, current: FileInfo) => void): Promise<UploadResult[]>;
/**
* Calculate total size of files
*/
declare function calculateTotalSize(files: FileInfo[]): number;
/**
* Format bytes to human-readable string
*/
declare function formatBytes(bytes: number): string;
/**
* Default state directory
*/
declare const DEFAULT_STATE_DIR = ".deploy";
/**
* State format version
*/
declare const STATE_VERSION = "1.0.0";
/**
* Get state file path
*/
declare function getStateFilePath(options?: StateOptions): string;
/**
* Check if state file exists
*/
declare function stateExists(options?: StateOptions): boolean;
/**
* Load state from file
*/
declare function loadState(options?: StateOptions): DeploymentState | null;
/**
* Save state to file
*/
declare function saveState(state: DeploymentState, options?: StateOptions): void;
/**
* Delete state file
*/
declare function deleteState(options?: StateOptions): boolean;
/**
* Initialize empty state
*/
declare function initializeState(app: string, environment?: string): DeploymentState;
/**
* Get or create state
*/
declare function getOrCreateState(app: string, options?: StateOptions): DeploymentState;
/**
* List all state files in directory
*/
declare function listStateFiles(stateDir?: string): string[];
/**
* Get state directory path
*/
declare function getStateDir(stateDir?: string): string;
/**
* Ensure state directory exists
*/
declare function ensureStateDir(stateDir?: string): void;
/**
* File state tracking
*
* Compares file hashes to detect changes for incremental deployment.
*/
/**
* Compare file hashes between current and previous state
*/
declare function compareFileHashes(currentFiles: FileInfo[], previousHashes: FileHashMap): FileChanges;
/**
* Get files that need to be uploaded (added + modified)
*/
declare function getFilesToUpload(currentFiles: FileInfo[], previousHashes: FileHashMap): FileInfo[];
/**
* Update file hashes in state
*/
declare function updateFileHashes(state: DeploymentState, files: FileInfo[]): DeploymentState;
/**
* Merge file hashes (add/update only, don't remove)
*/
declare function mergeFileHashes(state: DeploymentState, files: FileInfo[]): DeploymentState;
/**
* Remove deleted files from state
*/
declare function removeDeletedFiles(state: DeploymentState, deletedPaths: string[]): DeploymentState;
/**
* Get file hash from state
*/
declare function getFileHash(state: DeploymentState, filePath: string): string | undefined;
/**
* Check if file exists in state
*/
declare function hasFile(state: DeploymentState, filePath: string): boolean;
/**
* Get all file paths from state
*/
declare function getFilePaths(state: DeploymentState): string[];
/**
* Get file count from state
*/
declare function getFileCount(state: DeploymentState): number;
/**
* Create file hash map from FileInfo array
*/
declare function createFileHashMap(files: FileInfo[]): FileHashMap;
/**
* Format file changes for display
*/
declare function formatFileChanges(changes: FileChanges): string;
/**
* Calculate incremental deployment stats
*/
declare function getIncrementalStats(changes: FileChanges): {
needsUpload: number;
canSkip: number;
needsDelete: number;
totalChanges: number;
};
/**
* Resource state management
*
* Tracks AWS resource metadata (S3 buckets, CloudFront distributions).
*/
/**
* Update S3 resource state
*/
declare function updateS3Resource(state: DeploymentState, resource: S3ResourceState): DeploymentState;
/**
* Update CloudFront resource state
*/
declare function updateCloudFrontResource(state: DeploymentState, resource: CloudFrontResourceState): DeploymentState;
/**
* Update both S3 and CloudFront resources
*/
declare function updateResources(state: DeploymentState, resources: ResourcesState): DeploymentState;
/**
* Get S3 resource state
*/
declare function getS3Resource(state: DeploymentState): S3ResourceState | undefined;
/**
* Get CloudFront resource state
*/
declare function getCloudFrontResource(state: DeploymentState): CloudFrontResourceState | undefined;
/**
* Check if S3 resource exists in state
*/
declare function hasS3Resource(state: DeploymentState): boolean;
/**
* Check if CloudFront resource exists in state
*/
declare function hasCloudFrontResource(state: DeploymentState): boolean;
/**
* Remove S3 resource from state
*/
declare function removeS3Resource(state: DeploymentState): DeploymentState;
/**
* Remove CloudFront resource from state
*/
declare function removeCloudFrontResource(state: DeploymentState): DeploymentState;
/**
* Clear all resources from state
*/
declare function clearResources(state: DeploymentState): DeploymentState;
/**
* Create S3 resource state from deployment stats
*/
declare function createS3ResourceState(bucketName: string, region: string, websiteUrl?: string): S3ResourceState;
/**
* Create CloudFront resource state
*/
declare function createCloudFrontResourceState(distributionId: string, domainName: string, distributionUrl: string, aliases?: string[]): CloudFrontResourceState;
/**
* Get resource summary for display
*/
declare function getResourceSummary(state: DeploymentState): {
hasS3: boolean;
hasCloudFront: boolean;
s3BucketName?: string;
s3Region?: string;
distributionId?: string;
distributionUrl?: string;
};
/**
* Format resource summary for display
*/
declare function formatResourceSummary(state: DeploymentState): string;
/**
* Check if state has any resources
*/
declare function hasAnyResource(state: DeploymentState): boolean;
/**
* Get all resource identifiers for cleanup
*/
declare function getResourceIdentifiers(state: DeploymentState): {
s3BucketName?: string;
s3Region?: string;
distributionId?: string;
};
/**
* Validate resource state consistency
*/
declare function validateResourceState(state: DeploymentState): {
valid: boolean;
errors: string[];
};
export { type AWSAccountInfo, type AWSCredentials, type AWSCredentialsConfig, type ClientOptions, type CloudFrontConfig, type CloudFrontDeploymentOptions, type CloudFrontDeploymentResult, type CloudFrontResourceState, type CreateDistributionOptions, type CredentialResolution, type CredentialSource, DEFAULT_STATE_DIR, type DeploymentState, type DeploymentStats, type EnvironmentConfig, type FileChange, type FileChanges, type FileHashMap, type FileInfo, type InvalidationOptions, type LoadConfigOptions, type ResourcesState, type S3Config, type S3ResourceState, type SCFConfig, STATE_VERSION, type ScanOptions, type StateManagerOptions, type StateOptions, type UploadOptions, type UploadResult, bucketExists, calculateFileHash, calculateTotalSize, clearResources, compareFileHashes, configureBucketWebsite, createBucket, createCloudFrontClient, createCloudFrontClientWithOptions, createCloudFrontResourceState, createCredentialProvider, createDistribution, createFileHashMap, createInvalidation, createS3Client, createS3ClientWithOptions, createS3ResourceState, createSTSClient, defineConfig, deleteState, deployToCloudFront, deployToS3, deployWithCloudFront, distributionExists, ensureBucket, ensureStateDir, filterChangedFiles, formatAccountInfo, formatBytes, formatFileChanges, formatResourceSummary, generateExampleConfig, getBucketWebsiteUrl, getCloudFrontResource, getCredentials, getDistribution, getDistributionDomainName, getDistributionUrl, getFileCount, getFileHash, getFilePaths, getFilesToUpload, getIncrementalStats, getInvalidation, getInvalidationStatus, getOrCreateState, getResourceIdentifiers, getResourceSummary, getS3Resource, getStateDir, getStateFilePath, groupFilesByCompression, hasAnyResource, hasCloudFrontResource, hasFile, hasS3Resource, initializeState, invalidateAll, invalidateCache, isInvalidationComplete, listStateFiles, loadConfig, loadState, mergeFileHashes, removeCloudFrontResource, removeDeletedFiles, removeS3Resource, saveState, scanFiles, setBucketPublicReadPolicy, stateExists, updateCloudFrontResource, updateDistribution, updateFileHashes, updateResources, updateS3Resource, uploadFile, uploadFiles, validateResourceState, verifyCredentials, waitForDistributionDeployed, waitForInvalidationCompleted };