in installer/src/main/java/com/amazon/aws/partners/saasfactory/saasboost/SaaSBoostInstall.java [1691:1822]
protected String buildAndCopyWebApp() {
Path webDir = workingDir.resolve(Path.of("client", "web"));
if (!Files.isDirectory(webDir)) {
outputMessage("Error, can't find client/web directory at " + webDir.toAbsolutePath().toString());
System.exit(2);
}
/*
REACT_APP_SIGNOUT_URI saas-boost::${ENVIRONMENT}-${AWS_REGION}:webUrl
REACT_APP_CALLBACK_URI saas-boost::${ENVIRONMENT}-${AWS_REGION}:webUrl
REACT_APP_COGNITO_USERPOOL saas-boost::${ENVIRONMENT}-${AWS_REGION}:userPoolId
REACT_APP_CLIENT_ID saas-boost::${ENVIRONMENT}-${AWS_REGION}:userPoolClientId
REACT_APP_COGNITO_USERPOOL_BASE_URI saas-boost::${ENVIRONMENT}-${AWS_REGION}:cognitoBaseUri
REACT_APP_API_URI saas-boost::${ENVIRONMENT}-${AWS_REGION}:publicApiUrl
WEB_BUCKET saas-boost::${ENVIRONMENT}-${AWS_REGION}:webBucket
*/
String nextToken = null;
Map<String, String> exportsMap = new HashMap<>();
try {
do {
ListExportsResponse response = cfn.listExports(ListExportsRequest.builder()
.nextToken(nextToken)
.build());
nextToken = response.nextToken();
for (Export export : response.exports()) {
if (export.name().startsWith("saas-boost::" + envName)) {
exportsMap.put(export.name(), export.value());
}
}
} while (nextToken != null);
} catch (SdkServiceException cfnError) {
LOGGER.error("cloudformation:ListExports error", cfnError);
LOGGER.error(getFullStackTrace(cfnError));
throw cfnError;
}
final String prefix = "saas-boost::" + envName + "-" + AWS_REGION.id() + ":";
// We need to know the CloudFront distribution URL to continue
final String webUrl = exportsMap.get(prefix + "webUrl");
if (isEmpty(webUrl)) {
outputMessage("Unexpected errors, CloudFormation export " + prefix + "webUrl not found");
LOGGER.info("Available exports part of stack output" + String.join(", ", exportsMap.keySet()));
System.exit(2);
}
// We need to know the S3 bucket to host the web files in to continue
final String webBucket = exportsMap.get(prefix + "webBucket");
if (isEmpty(webBucket)) {
outputMessage("Unexpected errors, CloudFormation export " + prefix + "webBucket not found");
LOGGER.info("Available exports part of stack output" + String.join(", ", exportsMap.keySet()));
System.exit(2);
}
// Execute yarn build to generate the React app
outputMessage("Start build of AWS SaaS Boost React web application with yarn...");
ProcessBuilder pb;
Process process = null;
try {
if (isWindows()) {
pb = new ProcessBuilder("cmd", "/c", "yarn", "build");
} else {
pb = new ProcessBuilder("yarn", "build");
}
Map<String, String> env = pb.environment();
pb.directory(webDir.toFile());
env.put("REACT_APP_SIGNOUT_URI", webUrl);
env.put("REACT_APP_CALLBACK_URI", webUrl);
env.put("REACT_APP_COGNITO_USERPOOL", exportsMap.get(prefix + "userPoolId"));
env.put("REACT_APP_CLIENT_ID", exportsMap.get(prefix + "userPoolClientId"));
env.put("REACT_APP_COGNITO_USERPOOL_BASE_URI", exportsMap.get(prefix + "cognitoBaseUri"));
env.put("REACT_APP_API_URI", exportsMap.get(prefix + "publicApiUrl"));
env.put("REACT_APP_AWS_ACCOUNT", accountId);
env.put("REACT_APP_ENVIRONMENT", envName);
env.put("REACT_APP_AWS_REGION", AWS_REGION.id());
process = pb.start();
printResults(process);
process.waitFor();
int exitValue = process.exitValue();
if (exitValue != 0) {
throw new RuntimeException("Error building web application. Verify version of Node is correct.");
}
outputMessage("Completed build of AWS SaaS Boost React web application.");
} catch (IOException | InterruptedException e) {
LOGGER.error(getFullStackTrace(e));
} finally {
if (process != null) {
process.destroy();
}
}
// Sync files to the web bucket
outputMessage("Synchronizing AWS SaaS Boost web application files to s3 web bucket");
cleanUpS3(webBucket, "");
Map<String, String> metadata = Stream
.of(new AbstractMap.SimpleEntry<>("Cache-Control", "no-store"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Path yarnBuildDir = webDir.resolve(Path.of("build"));
List<Path> filesToUpload;
try (Stream<Path> stream = Files.walk(yarnBuildDir)) {
filesToUpload = stream.filter(Files::isRegularFile).collect(Collectors.toList());
outputMessage("Uploading " + filesToUpload.size() + " files to S3");
for (Path fileToUpload : filesToUpload) {
// Remove the parent client/web/build path from the S3 key
String key = fileToUpload.subpath(yarnBuildDir.getNameCount(), fileToUpload.getNameCount()).toString();
try {
// TODO this really should be a delete and copy like aws s3 sync --delete
LOGGER.info("Uploading to S3 " + fileToUpload.toString() + " -> " + key);
s3.putObject(PutObjectRequest.builder()
.bucket(webBucket)
// java.nio.file.Path will use OS dependent file separators, so when we run the installer on
// Windows, the S3 key will have back slashes instead of forward slashes. The CloudFormation
// definitions of Lambda functions will always use forward slashes for the S3Key property.
.key(key.replace('\\', '/'))
.metadata(metadata)
.build(), RequestBody.fromFile(fileToUpload)
);
} catch (SdkServiceException s3Error) {
LOGGER.error("s3:PutObject error", s3Error);
LOGGER.error(getFullStackTrace(s3Error));
throw s3Error;
}
}
} catch (IOException ioe) {
LOGGER.error("Error walking web app build directory", ioe);
LOGGER.error(getFullStackTrace(ioe));
throw new RuntimeException(ioe);
}
return webUrl;
}