Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/jsdoc-to-markdown-…
Browse files Browse the repository at this point in the history
…8.0.0
  • Loading branch information
shazron authored Aug 15, 2023
2 parents 2b1c424 + eac26a3 commit 676fedc
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 28 deletions.
42 changes: 42 additions & 0 deletions lib/StorageError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const { ErrorWrapper, createUpdater } = require('@adobe/aio-lib-core-errors').AioCoreSDKErrorWrapper
const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-web', { provider: 'debug' })

const codes = {}
const messages = new Map()

const Updater = createUpdater(
codes,
messages
)

const E = ErrorWrapper(
'WebStorageError',
'WebLib',
Updater
)

E('ERROR_INVALID_HEADER_NAME', '`%s` is not a valid response header name')
E('ERROR_INVALID_HEADER_VALUE', '`%s` is not a valid response header value for `%s`')

function logAndThrow (e) {
logger.error(JSON.stringify(e, null, 2))
throw e
}

module.exports = {
codes,
messages,
logAndThrow
}
78 changes: 75 additions & 3 deletions lib/remote-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const mime = require('mime-types')
const fs = require('fs-extra')
const joi = require('joi')
const klaw = require('klaw')
const http = require('http')
const { codes, logAndThrow } = require('./StorageError')

const fileExtensionPattern = /\*\.[0-9a-zA-Z]+$/

// /**
// * Joins url path parts
Expand Down Expand Up @@ -122,20 +126,26 @@ module.exports = class RemoteStorage {
* @param {string} file
* @param {string} prefix - prefix to upload the file to
* @param {Object} appConfig - application config
* @param {string} distRoot - Distribution root dir
*/
async uploadFile (file, prefix, appConfig) {
async uploadFile (file, prefix, appConfig, distRoot) {
if (typeof prefix !== 'string') {
throw new Error('prefix must be a valid string')
}
const content = await fs.readFile(file)
const mimeType = mime.lookup(path.extname(file))
const cacheControlString = this._getCacheControlConfig(mimeType, appConfig)
const cacheControlString = this._getCacheControlConfig(mimeType, appConfig.app)
const uploadParams = {
Bucket: this.bucket,
Key: urlJoin(prefix, path.basename(file)),
Body: content,
CacheControl: cacheControlString
}
// add response headers if specified in manifest
const responseHeaders = this.getResponseHeadersForFile(file, distRoot, appConfig)
if (responseHeaders) {
uploadParams.Metadata = responseHeaders
}
// s3 misses some mime types like for css files
if (mimeType) {
uploadParams.ContentType = mimeType
Expand All @@ -146,6 +156,68 @@ module.exports = class RemoteStorage {
return this.s3.putObject(uploadParams)
}

getResponseHeadersForFile (file, distRoot, appConfig) {
let responseHeaders
if (appConfig.web && appConfig.web['response-headers']) {
responseHeaders = {}
const cdnConfig = appConfig.web['response-headers']
const headerPrefix = 'adp-'

Object.keys(cdnConfig).forEach(rule => {
if (this.canAddHeader(file, distRoot, rule)) {
Object.keys(cdnConfig[rule]).forEach(header => {
this.validateHTTPHeader(header, cdnConfig[rule][header])
responseHeaders[headerPrefix + header] = cdnConfig[rule][header]
})
}
})
}
return responseHeaders
}

canAddHeader (file, distRoot, rule) {
const filePath = path.parse(file)
const normalisedRule = rule.replace(/\//g, path.sep)
const ruleFolderPath = path.parse(normalisedRule)
let folderPathToMatch = path.join(distRoot, ruleFolderPath.dir)
if (folderPathToMatch.endsWith(path.sep)) {
folderPathToMatch = folderPathToMatch.substring(0, folderPathToMatch.length - 1) // remove any trailing path separator
}

if (rule === '/*') { // all content
return true
} else if (rule.endsWith('/*')) { // all content in a folder ex. /test/*
if (filePath.dir.startsWith(folderPathToMatch)) { // matches with the folder
return true
}
} else if (fileExtensionPattern.test(rule)) { // all content with a given extension ex. /*.html or /test/*.js
// check file has same extension as specified in header
if ((filePath.ext === ruleFolderPath.ext) && (filePath.dir.startsWith(folderPathToMatch))) {
return true
}
} else { // specific file match ex. /test/foo.js
const uploadFilePath = path.join(distRoot, normalisedRule)
if (file === uploadFilePath) {
return true
}
}
return false
}

validateHTTPHeader (headerName, value) {
try {
http.validateHeaderName(headerName)
} catch (e) {
logAndThrow(new codes.ERROR_INVALID_HEADER_NAME({ messageValues: [headerName], sdkDetails: {} }))
}

try {
http.validateHeaderValue(headerName, value)
} catch (e) {
logAndThrow(new codes.ERROR_INVALID_HEADER_VALUE({ messageValues: [value, headerName], sdkDetails: {} }))
}
}

async walkDir (dir) {
return new Promise((resolve, reject) => {
const items = []
Expand Down Expand Up @@ -181,7 +253,7 @@ module.exports = class RemoteStorage {
prefixDirectory = prefixDirectory === '.' ? '' : prefixDirectory
// newPrefix is now the initial prefix plus the files relative directory path.
const newPrefix = urlJoin(prefix, prefixDirectory)
const s3Res = await this.uploadFile(f, newPrefix, appConfig)
const s3Res = await this.uploadFile(f, newPrefix, appConfig, dir)

if (postFileUploadCallback) {
postFileUploadCallback(f)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/aio-lib-web",
"version": "6.0.2",
"version": "6.1.1",
"description": "Utility tooling library to build and deploy Adobe I/O Project Firefly app static sites to CDN",
"main": "index.js",
"directories": {
Expand Down
2 changes: 1 addition & 1 deletion src/deploy-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const deployWeb = async (config, log) => {
await remoteStorage.emptyFolder(config.s3.folder + '/')
}
const _log = log ? (f) => log(`deploying ${path.relative(dist, f)}`) : null
await remoteStorage.uploadDir(dist, config.s3.folder, config.app, _log)
await remoteStorage.uploadDir(dist, config.s3.folder, config, _log)

const url = `https://${config.ow.namespace}.${config.app.hostname}/index.html`
return url
Expand Down
4 changes: 2 additions & 2 deletions src/undeploy-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ const undeployWeb = async (config) => {

const remoteStorage = new RemoteStorage(creds)

if (!(await remoteStorage.folderExists(config.s3.folder))) {
if (!(await remoteStorage.folderExists(config.s3.folder + '/'))) {
throw new Error(`cannot undeploy static files, there is no deployment for ${config.s3.folder}`)
}

await remoteStorage.emptyFolder(config.s3.folder)
await remoteStorage.emptyFolder(config.s3.folder + '/')
}

module.exports = undeployWeb
1 change: 0 additions & 1 deletion test/__mocks__/@adobe/aio-lib-core-tvm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

const mockTvm = {
init: jest.fn(async () => {
return {
Expand Down
15 changes: 14 additions & 1 deletion test/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ global.configWithMissing = (config, members) => {
return config
}

global.configWithModifiedWeb = (config, newWebConfig) => {
config = cloneDeep(config)
config.web = newWebConfig
return config
}

global.fakeS3Bucket = 'fake-bucket'
global.fakeConfig = {
tvm: {
Expand Down Expand Up @@ -139,11 +145,18 @@ global.fakeConfig = {
awssecretaccesskey: 'fakeAwsSecretKey'
}
},
cna: {
app: {
htmlCacheDuration: 60,
jsCacheDuration: 604800,
cssCacheDuration: 604800,
imageCacheDuration: 604800
},
web: {
'response-headers': {
'/*': {
testHeader: 'foo'
}
}
}
}

Expand Down
Loading

0 comments on commit 676fedc

Please sign in to comment.