UNPKG

@aws/cloudfront-hosting-toolkit

Version:

CloudFront Hosting Toolkit offers the convenience of a managed frontend hosting service while retaining full control over the hosting and deployment infrastructure to make it your own.

674 lines (670 loc) 94.3 kB
#!/usr/bin/env node "use strict"; /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateNoLeadingTrailingSlashes = exports.isValidBucketName = exports.isS3Config = exports.isRepoConfig = exports.checkPipelineStatus = exports.startPrompt = exports.getDomainNames = exports.executeCommands = exports.doesHostingConfigurationFileExist = exports.loadHostingConfiguration = exports.getConfigFileContent = exports.detectFrontendFramework = exports.getGitBranch = exports.getGitRemote = exports.findIndex = exports.isValidDomainName = exports.isValidGithubUrl = exports.frameworkList = exports.getNiceFrameworkLabel = exports.parseRepositoryUrl = exports.calculateCodeStarConnectionStackName = exports.cleanActionNameStr = exports.cleanBuildNameStr = exports.cleanPipelineNameStr = exports.calculateConnectionStackName = exports.calculateMainStackName = exports.getBuildConfigS3Folder = exports.getCffTemplatesFolder = exports.getBuildConfigTemplatesFolder = exports.getToolFolder = exports.getConfigFilePath = exports.getCffConfigFilePath = exports.getBuildConfigFilePath = exports.getCLIInstallationFolder = exports.getCLIExecutionFolder = void 0; const path = __importStar(require("path")); const fs_1 = __importDefault(require("fs")); const fs_2 = require("fs"); const cliProgress = require("cli-progress"); const { spawn } = require("child_process"); const prompts = require("prompts"); const types_1 = require("../shared/types"); const constants_1 = require("../shared/constants"); const awsSDKUtil_1 = require("./awsSDKUtil"); const MAX_STACK_NAME_LENGTH = 128; const MAX_PIPELINE_NAME_LENGTH = 100; const MAX_BUILD_NAME_LENGTH = 150; const MAX_ACTION_NAME_LENGTH = 100; /** * Retrieves the absolute path of the current working directory. * @returns The absolute path of the current working directory as a string. */ function getCLIExecutionFolder() { return process.cwd(); } exports.getCLIExecutionFolder = getCLIExecutionFolder; /** * Detects the root folder of the CDK project based on the presence of 'package.json' * and the 'name' property in the project's package.json file. * The function checks the current directory and the 'cloudfront-hosting-toolkit' subdirectory to find the project root. * @returns The path to the root folder of the CloudFront Pages CDK project, if found, or the absolute path to the current directory if not found. */ function getCLIInstallationFolder() { return path.resolve(__dirname, "..", "..", ".."); } exports.getCLIInstallationFolder = getCLIInstallationFolder; /** * Retrieves the file path of the build configuration used to construct the website before deployment. * The function combines the current working directory path and the relevant file names to construct the path. * @returns The absolute file path of the build configuration used for website construction. */ function getBuildConfigFilePath() { return path.join(getCLIExecutionFolder(), constants_1.TOOL_NAME) + "/" + constants_1.BUILD_FILE_NAME; } exports.getBuildConfigFilePath = getBuildConfigFilePath; function getCffConfigFilePath() { return path.join(getCLIExecutionFolder(), constants_1.TOOL_NAME) + "/" + constants_1.CFF_FILE_NAME; } exports.getCffConfigFilePath = getCffConfigFilePath; /** * Retrieves the file path of the configuration file used for the deployment of the hosting infrastructure. * The function combines the current working directory path and the relevant file names to construct the path. * @returns The absolute file path of the deployment configuration file. */ function getConfigFilePath() { return path.join(getCLIExecutionFolder(), constants_1.TOOL_NAME) + "/" + constants_1.CONFIG_FILE_NAME; } exports.getConfigFilePath = getConfigFilePath; /** * Retrieves the absolute path of the folder where the hosting infrastructure tool is located. * The function combines the current working directory path and the name of the tool folder to construct the path. * @returns The absolute path of the hosting infrastructure tool folder. */ function getToolFolder() { return path.join(getCLIExecutionFolder(), constants_1.TOOL_NAME); } exports.getToolFolder = getToolFolder; //return the folder where all the build configuration are stored: [CDK]/resources/build_config_templates function getBuildConfigTemplatesFolder() { return path.join(getCLIInstallationFolder(), "resources", "build_config_templates"); } exports.getBuildConfigTemplatesFolder = getBuildConfigTemplatesFolder; function getCffTemplatesFolder() { return path.join(getCLIInstallationFolder(), "resources", "cff_templates"); } exports.getCffTemplatesFolder = getCffTemplatesFolder; function getBuildConfigS3Folder() { return path.join(getCLIInstallationFolder(), "resources", "s3_trigger"); } exports.getBuildConfigS3Folder = getBuildConfigS3Folder; function getLogFilePath() { const now = new Date(); const filename = `${now.getFullYear()}-${(now.getMonth() + 1) .toString() .padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}_${now .getHours() .toString() .padStart(2, "0")}-${now.getMinutes().toString().padStart(2, "0")}-${now .getSeconds() .toString() .padStart(2, "0")}.log`; return path.join(getCLIExecutionFolder(), constants_1.TOOL_NAME) + "/" + filename; } /** * Calculates the main stack name based on the provided hosting configuration. * * The stack name is generated using the following criteria: * - If the configuration corresponds to a repository, the stack name will be derived * from the repository's name and the branch name. * - If the configuration corresponds to an S3 bucket, the stack name will use the bucket's name. * * The resultant stack name will: * - Begin with an alphabetic character. * - Contain only alphanumeric characters and hyphens. * - Be no longer than 128 characters. * - Not end with a hyphen. * * @param hostingConfiguration - The hosting configuration from which to derive the stack name. * @returns A sanitized stack name conforming to the above criteria. * @throws {Error} Throws an error if the provided repository URL is invalid. */ function calculateMainStackName(hostingConfiguration) { let result; if (isRepoConfig(hostingConfiguration)) { const parsedUrl = parseRepositoryUrl(hostingConfiguration.repoUrl); const { repoName } = parsedUrl; result = constants_1.MAIN_STACK_NAME + "-" + repoName + "-" + hostingConfiguration.branchName; } else { result = hostingConfiguration.s3bucket; } result = cleanStackNameStr(result); return result; } exports.calculateMainStackName = calculateMainStackName; /** * Constructs a main stack name based on repository owner, repository name, and branch name. * If the resulting stack name exceeds 128 characters, it truncates to fit within the limit. * The generated stack name: * - Begins with an alphabetic character. * - Contains only alphanumeric characters and hyphens. * - Is no longer than 128 characters. * * @param {string} repoOwner - The owner of the repository. * @param {string} repoName - The name of the repository. * @param {string} branchName - The name of the branch. * @returns {string} The sanitized stack name conforming to the criteria. */ function calculateConnectionStackName(repoUrl, branchName) { const parsedUrl = parseRepositoryUrl(repoUrl); const { repoOwner, repoName } = parsedUrl; var desiredString = `${constants_1.CONNECTION_STACK_NAME}-${repoName}-${branchName}-${repoOwner}`; return cleanStackNameStr(desiredString); } exports.calculateConnectionStackName = calculateConnectionStackName; function cleanNameStr(stackName, maxLength) { var desiredString = stackName.replace(/[^a-zA-Z0-9]/g, "-"); desiredString = truncateString(desiredString, maxLength); // Ensure it doesn't end with a hyphen after truncation while (desiredString.endsWith("-")) { desiredString = desiredString.substring(0, desiredString.length - 1); } // Ensure it starts with an alphabetic character by prepending 'A' if not. if (!/^[a-zA-Z]/.test(desiredString)) { desiredString = "A" + desiredString.substring(1); } return desiredString; } function cleanStackNameStr(stackName) { return cleanNameStr(stackName, MAX_STACK_NAME_LENGTH); } function cleanPipelineNameStr(stackName) { return cleanNameStr(stackName, MAX_PIPELINE_NAME_LENGTH); } exports.cleanPipelineNameStr = cleanPipelineNameStr; function cleanBuildNameStr(stackName) { return cleanNameStr(stackName, MAX_BUILD_NAME_LENGTH); } exports.cleanBuildNameStr = cleanBuildNameStr; function cleanActionNameStr(stackName) { return cleanNameStr(stackName, MAX_ACTION_NAME_LENGTH); } exports.cleanActionNameStr = cleanActionNameStr; function calculateCodeStarConnectionStackName(repoUrl, branchName) { const MAX_CODE_STAR_CONNECTION_LENGTH = 32; const parsedUrl = parseRepositoryUrl(repoUrl); const { repoOwner, repoName } = parsedUrl; const desiredString = `${repoName}-${branchName}-${repoOwner}`; return truncateString(desiredString, MAX_CODE_STAR_CONNECTION_LENGTH); } exports.calculateCodeStarConnectionStackName = calculateCodeStarConnectionStackName; /** * Parses the input repository URL to extract the repository owner and repository name. * The function supports both HTTPS and SSH GitHub repository URL formats. * @param url The repository URL to parse. * @returns An object with 'repoOwner' and 'repoName' properties if the URL matches the expected GitHub formats, */ function parseRepositoryUrl(url) { //github url parsing const regex = /^(https:\/\/github\.com\/([^/]+)\/([^/]+)\.git)|(git@github\.com:([^/]+)\/([^/]+)\.git)$/; const matches = url.match(regex); if (matches) { // HTTPS URL format if (matches[2] && matches[3]) { return { repoOwner: matches[2], repoName: matches[3] }; } // SSH URL format if (matches[5] && matches[6]) { return { repoOwner: matches[5], repoName: matches[6] }; } } console.log("Invalid repository URL format"); process.exit(1); } exports.parseRepositoryUrl = parseRepositoryUrl; function getNiceFrameworkLabel(key) { return key in constants_1.FRAMEWORKS ? constants_1.FRAMEWORKS[key] : key; } exports.getNiceFrameworkLabel = getNiceFrameworkLabel; /** * Retrieves a list of available hosting frameworks from the build_config_templates directory. * The function reads the hosting configuration template files with names starting with "hosting_" and ending with ".yml". * Each detected framework is added to a list as an object with 'title' and 'value' properties. * The 'title' represents the nice label for the framework, and the 'value' represents the framework name used internally. * The function also adds an "Exit" option to the list to allow users to exit the framework selection. * @returns An array of objects containing the available hosting frameworks, each with 'title' and 'value' properties. * If no frameworks are found, the array will be empty. */ function frameworkList() { const filePathIn = path.resolve(getCLIInstallationFolder(), "resources"); const inputFolder = path.join(filePathIn, "build_config_templates"); const files = fs_1.default.readdirSync(inputFolder); const regex = /^hosting_(.*?)\.yml$/; const frameworkList = []; const fileNames = files .filter((file) => regex.test(file)) .map((file) => file.match(regex)[1]); fileNames.forEach((name) => { frameworkList.push({ title: getNiceFrameworkLabel(name), value: name }); }); frameworkList.push({ title: "None from the list, exit and add my own", value: "exit", }); return frameworkList; } exports.frameworkList = frameworkList; /** * Checks if the provided string is a valid GitHub URL. * @param url The string to be checked for validity as a GitHub URL. * @returns true if the input is a valid GitHub URL, false otherwise. */ function isValidGithubUrl(url) { return constants_1.GITHUB_REGEX.test(url); } exports.isValidGithubUrl = isValidGithubUrl; /** * Checks if the provided string is a valid domain name. * @param domainName The string to be checked for validity as a domain name. * @returns true if the input is a valid domain name, false otherwise. */ function isValidDomainName(domainName) { return constants_1.DOMAIN_NAME_REGEX.test(domainName); } exports.isValidDomainName = isValidDomainName; /** * Find the index of a given search string in an array of objects. * @param searchString The string to search for in the array. * @param existingConfig The array of objects to search through. * @returns The index of the first object in the array whose 'value' property contains the search string (case-insensitive). * If no match is found, returns 0. */ function findIndex(searchString, existingConfig) { const match = existingConfig.find((item) => item.value.toLowerCase().includes(searchString.toLowerCase())); return match ? existingConfig.indexOf(match) : 0; } exports.findIndex = findIndex; /** * Retrieves the GitHub repository URL from the local git repository configuration at the specified folderPath. * The function reads the '.git/config' file in the repository to extract the remote repository URL. * @param folderPath The path to the local git repository folder where the '.git' directory is located. * @returns The GitHub repository URL (if found and valid) or an empty string if the URL cannot be detected or the repository is not a valid git repository. * The repository URL is extracted from the remote repository configuration in the '.git/config' file. */ function getGitRemote() { try { const folderPath = process.cwd(); const configPath = path.join(folderPath, ".git", "config"); const config = fs_1.default.readFileSync(configPath, "utf8"); const regex = /\[remote "(.*)"\]\s+url = (.*)/g; let match; while ((match = regex.exec(config)) !== null) { const remoteName = match[1]; const remoteUrl = match[2]; if (remoteUrl && isValidGithubUrl(remoteUrl)) { return remoteUrl; } } return ""; } catch (e) { return ""; } } exports.getGitRemote = getGitRemote; /** * Retrieves the GitHub branch associated with the local git repository configuration at the specified folderPath. * The function reads the '.git/config' file in the repository to extract the remote branch information. * @param folderPath The path to the local git repository folder where the '.git' directory is located. * @returns The GitHub branch name (if found) or undefined if the branch cannot be detected or the repository is not a valid git repository. * The branch name is extracted from the remote branch configuration in the '.git/config' file. */ function getGitBranch() { try { const folderPath = process.cwd(); const configPath = path.join(folderPath, ".git", "config"); const config = fs_1.default.readFileSync(configPath, "utf8"); const regex = /\[branch "(.*)"\]\s+remote =.*\s+merge = (.*)/g; let match; while ((match = regex.exec(config)) !== null) { const branchName = match[1]; const branchRef = match[2]; if (branchRef && branchRef.startsWith("refs/heads/")) { return branchRef.replace("refs/heads/", ""); } } return "main"; } catch (e) { return "main"; } } exports.getGitBranch = getGitBranch; /** * Detects the frontend framework utilized by examining the package.json file at the specified packagePath. * The function checks the presence of specific scripts and dependencies related to known frontend frameworks. * @param packagePath The path to the directory containing the package.json file to analyze. * @returns A Promise that resolves with the detected frontend framework (if found) or undefined if no recognized framework is detected. * If the package.json file is not found, it indicates that no frontend framework is used, and the return value will be FrontendFramework.NONE. */ async function detectFrontendFramework(packagePath) { const items = fs_1.default.readdirSync(packagePath); const files = items.filter((item) => { const itemPath = path.join(packagePath, item); return fs_1.default.statSync(itemPath).isFile(); }); if (files.length === 0) { return ""; // No files in the folder } if (!files.includes("package.json")) { return types_1.FrontendFramework.BASIC; } const file = await fs_1.default.promises.readFile(`${packagePath}/package.json`); const packageJson = JSON.parse(file.toString()); if (packageJson.scripts) { const scriptValues = Object.values(packageJson.scripts); if (scriptValues.some((value) => typeof value == "string" && value.includes("vue"))) { return types_1.FrontendFramework.VUE; } if (scriptValues.some((value) => typeof value == "string" && value.includes("next"))) { return types_1.FrontendFramework.NEXT; } if (scriptValues.some((value) => typeof value == "string" && value.includes("ng"))) { return types_1.FrontendFramework.ANGULAR; } } if (packageJson.dependencies) { if ("react" in packageJson.dependencies || "react" in packageJson.devDependencies) { return types_1.FrontendFramework.REACT; } if ("vue" in packageJson.dependencies || "vue" in packageJson.devDependencies) { return types_1.FrontendFramework.VUE; } if ("@angular/core" in packageJson.dependencies || "@angular/core" in packageJson.devDependencies) { return types_1.FrontendFramework.ANGULAR; } } return ""; } exports.detectFrontendFramework = detectFrontendFramework; /** * Reads and returns the configuration data from the build configuration file. * The build configuration file is expected to be in JSON format. * If the file is found and successfully read, its content is parsed and returned as an object. * If the file is not found or an error occurs during reading or parsing, an error message is logged, and the process exits with code 1. * @returns An object representing the configuration data read from the build configuration file. * @throws {Error} If the file is not found or cannot be read or parsed, an error is thrown. */ function getConfigFileContent() { try { const rawConfigData = fs_1.default.readFileSync(getBuildConfigFilePath()); console.error(`${getBuildConfigFilePath()} found `); return JSON.parse(rawConfigData.toString()); } catch (e) { console.error(`${constants_1.ERROR_PREFIX} ${getBuildConfigFilePath()} not found xx`); process.exit(1); } } exports.getConfigFileContent = getConfigFileContent; /** Loads the hosting configuration from a specified file path. The function first attempts to require and load the configuration from the given file path. If the configuration file is not found, it proceeds without an error but with an empty configuration object. The function then validates the loaded configuration object to ensure that it contains the required properties for a valid HostingConfiguration. Specifically, a valid configuration must either have 'repoUrl', 'branchName', and 'framework' properties, or 's3bucket' and 's3path' properties. @returns {HostingConfiguration} The loaded and validated hosting configuration. @throws {Error} Throws an error if the configuration format is invalid. */ const loadHostingConfiguration = async (configFile) => { let config = {}; // Initialize an empty config object try { const pathToUse = configFile || getConfigFilePath(); config = require(pathToUse); } catch (e) { console.error("Error", e); process.exit(1); } // Validate that the required properties for each HostingConfiguration shape are present if (("repoUrl" in config && "branchName" in config && "framework" in config) || ("s3bucket" in config && "s3path" in config)) { // Return the loaded configuration as HostingConfiguration return config; } else { throw new Error("Invalid configuration format."); // Handle invalid configuration format } }; exports.loadHostingConfiguration = loadHostingConfiguration; const doesHostingConfigurationFileExist = () => { const configFilePath = getConfigFilePath(); // Assuming getConfigFilePath() is a function you have defined elsewhere return fs_1.default.existsSync(configFilePath); }; exports.doesHostingConfigurationFileExist = doesHostingConfigurationFileExist; /** * Executes a command using the `spawn` function from the `child_process` module. * The command's progress and output are logged to a file and displayed as a progress bar. * @param command An object representing the command to be executed, containing `label` and `cmd` properties. * @param pwd The current working directory where the command should be executed. * @returns A Promise that resolves when the command is successfully executed, or rejects if it fails. * The function logs the command's output and progress to a file and displays a progress bar on the console. * If the command fails (exit code other than 0), an error is thrown with a detailed error message and the process exits with code 1. * The log file path is retrieved using `getLogFilePath()` function and is created or appended to as needed. * The progress bar is displayed using the `cli-progress` library. */ async function executeCommands(cmd, pwd) { const logFile = getLogFilePath(); const outputStream = (0, fs_2.createWriteStream)(logFile, { flags: "a" }); const label = "Please wait ..."; outputStream.write("══════════════════════\n"); outputStream.write(`${cmd}\n\n\n`); outputStream.write("══════════════════════\n\n"); var progressBar = new cliProgress.SingleBar({ format: `${label} | {bar} | {percentage}%` }, cliProgress.Presets.shades_classic); progressBar.start(100, 0); const child = spawn(cmd, { shell: true, cwd: pwd }); child.stdout.on("data", (data) => { outputStream.write(data); progressBar.increment(1); if (progressBar.value == 100) { progressBar.stop(); progressBar = new cliProgress.SingleBar({ format: `${label} | {bar} | {percentage}%` }, cliProgress.Presets.shades_classic); progressBar.start(100, 0); } }); child.stderr.on("data", (data) => { outputStream.write(data); progressBar.increment(1); if (progressBar.value == 100) { progressBar.stop(); progressBar = new cliProgress.SingleBar({ format: `${label} | {bar} | {percentage}%` }, cliProgress.Presets.shades_classic); progressBar.start(100, 0); } }); await new Promise((resolve, reject) => { child.on("exit", (code) => { if (code !== 0) { reject(new Error(`Command ${cmd} failed with error code ${code}`)); console.error(`${constants_1.ERROR_PREFIX} Command failed with error code ${code}.`); console.error(`Command: ${cmd}`); console.error(`A complete log of this run can be found in: ${logFile} \n\n`); process.exit(1); } else { progressBar.update(100); progressBar.stop(); resolve(); } }); }); outputStream.end(); } exports.executeCommands = executeCommands; /** * Checks if the given domain name starts with "www.". * @param domainName The domain name to check. * @returns A boolean value indicating whether the domainName starts with "www." (true) or not (false). */ function checkWWW(domainName) { const regex = /^www\./; return regex.test(domainName); } /** * Generates an array of domain names with and without "www." based on the given domainName. * If the domainName starts with "www.", it returns an array with both the original domainName and the one without "www.". * If the domainName does not start with "www.", it returns an array with both the original domainName and the one with "www." added. * @param domainName The domain name to process. * @returns An array of strings containing the original domainName and an alternate version with "www." added or removed. */ function getDomainNames(domainName) { if (checkWWW(domainName)) { return [domainName, domainName.replace("www.", "")]; } else { return [domainName, "www." + domainName]; } } exports.getDomainNames = getDomainNames; function truncateString(input, max_length) { if (input.length <= max_length) { return input; } const truncatedString = input.slice(0, max_length); return truncatedString; } /** * Starts a prompt with cancellation handling. * * This function initiates a user prompt with the specified question and automatically * handles cancellation by exiting the Command-Line Interface (CLI) if the user cancels * the prompt. It is designed for use cases where graceful handling of cancellation * is desired. * * @param question The prompt question object to be displayed. * @returns A promise that resolves with the user's response to the prompt. */ async function startPrompt(question) { console.log("\n"); return prompts.prompt(question, { onCancel: (prompt) => { console.log("Exiting the Command-Line Interface (CLI)."); process.exit(1); }, }); } exports.startPrompt = startPrompt; async function checkPipelineStatus() { let pipelineStatus; //console.log("\nChecking the status of the pipeline...\n"); let displayInProgressMessage = true; var progressBar; do { pipelineStatus = await (0, awsSDKUtil_1.getPipelineStatus)(); if (pipelineStatus.status === "InProgress") { if (displayInProgressMessage) { console.log("The pipeline is in progress, waiting for the pipeline to finish...\n"); displayInProgressMessage = false; progressBar = new cliProgress.SingleBar({ format: `Please wait ... | {bar} | {percentage}%` }, cliProgress.Presets.shades_classic); progressBar.start(100, 0); } //console.log("Pipeline is still in progress. Checking again in 20 seconds..."); progressBar.increment(1); if (progressBar.value == 100) { progressBar.stop(); progressBar = new cliProgress.SingleBar({ format: `Please wait ... | {bar} | {percentage}%` }, cliProgress.Presets.shades_classic); progressBar.start(100, 0); } await new Promise((resolve) => setTimeout(resolve, 10000)); } } while (pipelineStatus.status === "InProgress"); if (!displayInProgressMessage) { progressBar.update(100); progressBar.stop(); } if (pipelineStatus.status === "Failed") { const pipelineName = await (0, awsSDKUtil_1.getSSMParameter)(constants_1.SSM_PIPELINENAME_STR); console.log(`\n\n *** ERROR ***\n`); console.log(`\nThe pipeline execution encountered an error on the stage '${pipelineStatus.stageName}'.`); console.log("To investigate and resolve the issue, please follow these steps:\n"); console.log("A. Explore our troubleshooting section https://github.com/awslabs/cloudfront-hosting-toolkit/blob/main/docs/troubleshooting.md\n\n"); console.log("B. Inspect the pipeline execution details\n"); console.log(" 1. Visit the AWS Management Console."); console.log(" 2. Navigate to AWS CodePipeline."); console.log(` 3. Select the "${pipelineName}" pipeline.`); console.log(" 4. Inspect the pipeline execution details and logs for error messages."); console.log(" 5. Take the necessary actions to address the error."); console.log(" 6. Once resolved, you can trigger a new pipeline execution by choosing 'Release change' on the AWS Console. \n\n"); // You can add additional actions here if needed. } else { console.log("Current pipeline status: " + pipelineStatus.status); // Now you can handle the non-InProgress status here. } } exports.checkPipelineStatus = checkPipelineStatus; function isRepoConfig(config) { return "repoUrl" in config && "branchName" in config && "framework" in config; } exports.isRepoConfig = isRepoConfig; // Type guard for the S3 configuration function isS3Config(config) { return "s3bucket" in config && "s3path" in config; } exports.isS3Config = isS3Config; function isValidBucketName(bucketName) { const bucketNamePattern = /^[a-z0-9.-]+$/; const minLength = 3; const maxLength = 63; if (bucketName.length < minLength || bucketName.length > maxLength || !bucketName.match(bucketNamePattern) || !bucketName.match(/^[a-z0-9]/) || // Must start with a letter or number !bucketName.match(/[a-z0-9]$/) || // Must end with a letter or number bucketName.includes('..') || // Must not contain two adjacent periods bucketName.match(/^\d+\.\d+\.\d+\.\d+$/) || // Must not be an IP address bucketName.startsWith('xn--') || // Must not start with the prefix xn-- bucketName.startsWith('sthree-') || // Must not start with the prefix sthree- bucketName.startsWith('sthree-configurator') || // Must not start with the prefix sthree-configurator bucketName.endsWith('-s3alias') || // Must not end with -s3alias bucketName.endsWith('--ol-s3') // Must not end with --ol-s3 ) { return 'Invalid bucket name'; } return true; } exports.isValidBucketName = isValidBucketName; function validateNoLeadingTrailingSlashes(inputString) { // Regular expression to match strings that start or end with a slash const regex = /^\/|\/$/g; // Test the input string against the regex return !regex.test(inputString); } exports.validateNoLeadingTrailingSlashes = validateNoLeadingTrailingSlashes; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaGVscGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0VBY0U7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUYsMkNBQTZCO0FBQzdCLDRDQUFvQjtBQUVwQiwyQkFBdUM7QUFFdkMsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBQzVDLE1BQU0sRUFBRSxLQUFLLEVBQUUsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUM7QUFDM0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRW5DLDJDQUt5QjtBQUN6QixtREFZNkI7QUFDN0IsNkNBQWtFO0FBRWxFLE1BQU0scUJBQXFCLEdBQUcsR0FBRyxDQUFDO0FBQ2xDLE1BQU0sd0JBQXdCLEdBQUcsR0FBRyxDQUFDO0FBQ3JDLE1BQU0scUJBQXFCLEdBQUcsR0FBRyxDQUFDO0FBQ2xDLE1BQU0sc0JBQXNCLEdBQUcsR0FBRyxDQUFDO0FBRW5DOzs7R0FHRztBQUNILFNBQWdCLHFCQUFxQjtJQUNuQyxPQUFPLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztBQUN2QixDQUFDO0FBRkQsc0RBRUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQWdCLHdCQUF3QjtJQUN0QyxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDbkQsQ0FBQztBQUZELDREQUVDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQWdCLHNCQUFzQjtJQUNwQyxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMscUJBQXFCLEVBQUUsRUFBRSxxQkFBUyxDQUFDLEdBQUcsR0FBRyxHQUFHLDJCQUFlLENBQUM7QUFDL0UsQ0FBQztBQUZELHdEQUVDO0FBRUQsU0FBZ0Isb0JBQW9CO0lBQ2xDLE9BQU8sSUFBSSxDQUFD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