diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 912f26a32b..d80876121f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,14 +30,17 @@ jobs:
pwa-kit:
strategy:
matrix:
- node: [14]
+ node: [14, 16]
npm: [6, 7, 8]
+ exclude:
+ - node: 16
+ npm: 6
runs-on: ubuntu-latest
env:
- # The "default" npm is the one that ships with a given version of node
- # node v14 uses npm@6, latest node v16 uses npm@8
+ # The "default" npm is the one that ships with a given version of node.
# For more: https://nodejs.org/en/download/releases/
- IS_DEFAULT_NPM: ${{ matrix.npm == 6 }}
+ IS_DEFAULT_NPM: ${{ matrix.node == 14 && matrix.npm == 6 || matrix.node == 16 && matrix.npm == 8 }}
+ IS_LATEST_NPM: ${{ matrix.node == 16 && matrix.npm == 8 }}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -68,28 +71,28 @@ jobs:
uses: "./.github/actions/smoke_tests"
- name: Create MRT credentials file
- if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOP == 'true'
+ if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.DEVELOP == 'true'
uses: "./.github/actions/create_mrt"
with:
mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }}
mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }}
- name: Push Bundle to MRT (Development)
- if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOP == 'true'
+ if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.DEVELOP == 'true'
uses: "./.github/actions/push_to_mrt"
with:
CWD: "./packages/template-retail-react-app"
TARGET: staging
- name: Push Bundle to MRT (Production)
- if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.RELEASE == 'true'
+ if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.RELEASE == 'true'
uses: "./.github/actions/push_to_mrt"
with:
CWD: "./packages/template-retail-react-app"
TARGET: production
- name: Push Bundle to MRT (Commerce SDK React)
- if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOPMENT == 'true'
+ if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.DEVELOPMENT == 'true'
uses: "./.github/actions/push_to_mrt"
with:
CWD: "./packages/test-commerce-sdk-react"
@@ -100,7 +103,7 @@ jobs:
uses: "./.github/actions/check_clean"
- name: Publish to NPM
- if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.RELEASE == 'true'
+ if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.RELEASE == 'true'
uses: "./.github/actions/publish_to_npm"
with:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
@@ -118,11 +121,16 @@ jobs:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
pwa-kit-windows:
strategy:
- # TODO: We don't *need* a matrix with single values,
- # but is it worth keeping for supporting multiple versions in the future?
- matrix:
- node: [14]
- npm: [6]
+ matrix:
+ node: [14, 16]
+ npm: [6, 7, 8]
+ exclude:
+ - node: 16
+ npm: 6
+ env:
+ # The "default" npm is the one that ships with a given version of node.
+ # For more: https://nodejs.org/en/download/releases/
+ IS_DEFAULT_NPM: ${{ matrix.node == 14 && matrix.npm == 6 || matrix.node == 16 && matrix.npm == 8 }}
runs-on: windows-latest
steps:
- name: Checkout
@@ -134,6 +142,11 @@ jobs:
node-version: ${{ matrix.node }}
cache: npm
+ - name: Update NPM version
+ if: env.IS_DEFAULT_NPM == 'false'
+ run: |-
+ npm install -g npm@${{ matrix.npm }}
+
- name: Setup Windows Machine
uses: "./.github/actions/setup_windows"
@@ -157,7 +170,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
- name: Setup Ubuntu Machine
uses: "./.github/actions/setup_ubuntu"
@@ -247,7 +260,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
- name: Setup Windows Machine
uses: "./.github/actions/setup_windows"
diff --git a/package.json b/package.json
index 31bbbb1791..876af3d82a 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "pwa-kit",
"version": "2.7.0-dev",
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"devDependencies": {
diff --git a/packages/commerce-sdk-react/package.json b/packages/commerce-sdk-react/package.json
index c2550a9dbe..5ba33efaeb 100644
--- a/packages/commerce-sdk-react/package.json
+++ b/packages/commerce-sdk-react/package.json
@@ -5,7 +5,7 @@
"author": "cc-pwa-kit@salesforce.com",
"license": "See license in LICENSE",
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"files": [
diff --git a/packages/internal-lib-build/configs/babel.config.js b/packages/internal-lib-build/configs/babel.config.js
index 79e63e0f08..30df8e4686 100644
--- a/packages/internal-lib-build/configs/babel.config.js
+++ b/packages/internal-lib-build/configs/babel.config.js
@@ -10,7 +10,7 @@ const config = {
require('@babel/preset-env'),
{
targets: {
- node: 14
+ node: 16
}
}
],
diff --git a/packages/internal-lib-build/package.json b/packages/internal-lib-build/package.json
index 84567f3f8e..315328d57c 100644
--- a/packages/internal-lib-build/package.json
+++ b/packages/internal-lib-build/package.json
@@ -2,7 +2,7 @@
"name": "internal-lib-build",
"version": "2.7.0-dev",
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"private": true,
diff --git a/packages/pwa-kit-create-app/README.md b/packages/pwa-kit-create-app/README.md
index 1ac70838b0..a4ca55f0c4 100644
--- a/packages/pwa-kit-create-app/README.md
+++ b/packages/pwa-kit-create-app/README.md
@@ -4,7 +4,7 @@ A tool for generating PWA Kit projects based on project templates, such as the [
## Requirements
-- Node 14
+- Node 14.17.0 or later
- npm 6.14.4 or later
## Quick Start
diff --git a/packages/pwa-kit-create-app/assets/pwa/default.js b/packages/pwa-kit-create-app/assets/pwa/default.js
index 687722489d..c5ff0b353d 100644
--- a/packages/pwa-kit-create-app/assets/pwa/default.js
+++ b/packages/pwa-kit-create-app/assets/pwa/default.js
@@ -65,7 +65,7 @@ module.exports = {
],
// Additional parameters that configure Express app behavior.
ssrParameters: {
- ssrFunctionNodeVersion: '14.x',
+ ssrFunctionNodeVersion: '16.x',
proxyConfigs: [
{
host: '${commerceApi.shortCode}.api.commercecloud.salesforce.com',
diff --git a/packages/pwa-kit-create-app/package.json b/packages/pwa-kit-create-app/package.json
index f63aa17c92..a367eafb40 100644
--- a/packages/pwa-kit-create-app/package.json
+++ b/packages/pwa-kit-create-app/package.json
@@ -26,7 +26,7 @@
"test": "internal-lib-build test"
},
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"dependencies": {
diff --git a/packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js b/packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js
index d7f47d2821..720dec503c 100755
--- a/packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js
+++ b/packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js
@@ -45,6 +45,7 @@ const p = require('path')
const sh = require('shelljs')
const fs = require('fs')
const cp = require('child_process')
+const semver = require('semver')
sh.set('-e')
@@ -147,7 +148,12 @@ const runGenerator = () => {
// Shelljs can't run interactive programs, so we have to switch to child_process.
// See https://github.com/shelljs/shelljs/wiki/FAQ#running-interactive-programs-with-exec
- cp.execSync(`npx pwa-kit-create-app ${process.argv.slice(2).join(' ')}`, {
+ const extension = process.platform === 'win32' ? '.cmd' : ''
+ const npm = `npm${extension}`
+ const foundNpm = cp.spawnSync(npm, ['-v']).stdout.toString().trim()
+ const flags = semver.satisfies(foundNpm, '>=7') ? '-y' : ''
+
+ cp.execSync(`npx ${flags} pwa-kit-create-app@latest ${process.argv.slice(2).join(' ')}`, {
stdio: 'inherit'
})
}
diff --git a/packages/pwa-kit-dev/CHANGELOG.md b/packages/pwa-kit-dev/CHANGELOG.md
index 13f15ccd76..c720c21eb8 100644
--- a/packages/pwa-kit-dev/CHANGELOG.md
+++ b/packages/pwa-kit-dev/CHANGELOG.md
@@ -1,4 +1,6 @@
## v2.7.0-dev (Jan 25, 2023)
+- Add explicit `ws` dependency [#865](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/865)
+
## v2.6.0 (Jan 25, 2023)
- Upgrade prettier to v2 [#926](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/926)
- Security package updates
diff --git a/packages/pwa-kit-dev/README.md b/packages/pwa-kit-dev/README.md
index 5bde8cbb9c..d718fa2bc3 100644
--- a/packages/pwa-kit-dev/README.md
+++ b/packages/pwa-kit-dev/README.md
@@ -4,7 +4,7 @@ A command-line tool to develop, build, and deploy PWA Kit projects.
## Requirements
-- Node 14
+- Node 14.17.0 or later
- npm 6.14.4 or later
To see all the available commands, run:
diff --git a/packages/pwa-kit-dev/package-lock.json b/packages/pwa-kit-dev/package-lock.json
index 36a39326b8..4e811a93d3 100644
--- a/packages/pwa-kit-dev/package-lock.json
+++ b/packages/pwa-kit-dev/package-lock.json
@@ -6849,6 +6849,13 @@
"whatwg-url": "^8.5.0",
"ws": "^7.4.6",
"xml-name-validator": "^3.0.0"
+ },
+ "dependencies": {
+ "ws": {
+ "version": "7.5.9",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
+ }
}
},
"jsesc": {
@@ -10722,6 +10729,11 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
+ },
+ "ws": {
+ "version": "7.5.9",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
}
}
},
@@ -11099,9 +11111,9 @@
}
},
"ws": {
- "version": "7.5.7",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
- "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A=="
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
+ "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig=="
},
"xml-name-validator": {
"version": "3.0.0",
diff --git a/packages/pwa-kit-dev/package.json b/packages/pwa-kit-dev/package.json
index e29d7367c2..6ec7600caf 100644
--- a/packages/pwa-kit-dev/package.json
+++ b/packages/pwa-kit-dev/package.json
@@ -104,7 +104,8 @@
"webpack-dev-middleware": "^5.2.2",
"webpack-hot-middleware": "^2.25.1",
"webpack-hot-server-middleware": "^0.6.1",
- "webpack-notifier": "^1.12.0"
+ "webpack-notifier": "^1.12.0",
+ "ws": "^8.12.0"
},
"devDependencies": {
"@loadable/component": "^5.15.0",
@@ -117,7 +118,7 @@
"@loadable/component": "^5.15.0"
},
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"publishConfig": {
diff --git a/packages/pwa-kit-dev/src/configs/babel/babel-config.js b/packages/pwa-kit-dev/src/configs/babel/babel-config.js
index 604da99a28..d42e41c9fe 100644
--- a/packages/pwa-kit-dev/src/configs/babel/babel-config.js
+++ b/packages/pwa-kit-dev/src/configs/babel/babel-config.js
@@ -11,7 +11,7 @@ const config = {
require('@babel/preset-env'),
{
targets: {
- node: 14
+ node: 16
}
}
],
diff --git a/packages/pwa-kit-dev/src/configs/webpack/config.js b/packages/pwa-kit-dev/src/configs/webpack/config.js
index b43cc9ceb8..9e2036f55b 100644
--- a/packages/pwa-kit-dev/src/configs/webpack/config.js
+++ b/packages/pwa-kit-dev/src/configs/webpack/config.js
@@ -9,7 +9,7 @@
// For more information on these settings, see https://webpack.js.org/configuration
import fs from 'fs'
-import path, {resolve} from 'path'
+import {resolve} from 'path'
import webpack from 'webpack'
import WebpackNotifierPlugin from 'webpack-notifier'
@@ -23,7 +23,6 @@ import {createModuleReplacementPlugin} from './plugins'
import {CLIENT, SERVER, CLIENT_OPTIONAL, SSR, REQUEST_PROCESSOR} from './config-names'
const projectDir = process.cwd()
-const sdkDir = resolve(path.join(__dirname, '..', '..', '..'))
const pkg = require(resolve(projectDir, 'package.json'))
const buildDir = process.env.PWA_KIT_BUILD_DIR
@@ -65,8 +64,21 @@ const entryPointExists = (segments) => {
}
const findInProjectThenSDK = (pkg) => {
- const projectPath = resolve(projectDir, 'node_modules', pkg)
- return fs.existsSync(projectPath) ? projectPath : resolve(sdkDir, 'node_modules', pkg)
+ // Look for the SDK node_modules in two places because in CI,
+ // pwa-kit-dev is published under a 'dist' directory, which
+ // changes this file's location relative to the package root.
+ const candidates = [
+ resolve(projectDir, 'node_modules', pkg),
+ resolve(__dirname, '..', '..', 'node_modules', pkg),
+ resolve(__dirname, '..', '..', '..', 'node_modules', pkg)
+ ]
+ let candidate
+ for (candidate of candidates) {
+ if (fs.existsSync(candidate)) {
+ return candidate
+ }
+ }
+ return candidate
}
const baseConfig = (target) => {
diff --git a/packages/pwa-kit-dev/src/utils/script-utils.ts b/packages/pwa-kit-dev/src/utils/script-utils.ts
index 3bbd53620d..53cdc1f492 100644
--- a/packages/pwa-kit-dev/src/utils/script-utils.ts
+++ b/packages/pwa-kit-dev/src/utils/script-utils.ts
@@ -119,6 +119,11 @@ export class CloudAPIClient {
error = {} // Cloud doesn't always return JSON
}
+ if (res.status === 403) {
+ error.docs_url =
+ 'https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/mrt-overview.html#users,-abilities,-and-roles'
+ }
+
throw new Error(
[
`HTTP ${res.status}`,
diff --git a/packages/pwa-kit-react-sdk/README.md b/packages/pwa-kit-react-sdk/README.md
index a3af8fbecd..b3d8dd4d8e 100644
--- a/packages/pwa-kit-react-sdk/README.md
+++ b/packages/pwa-kit-react-sdk/README.md
@@ -6,7 +6,7 @@ A library of components and utilities that supports the rendering pipeline for t
## Requirements
-- Node 14
+- Node 14.17.0 or later
- npm 6.14.4 or later
## Install Dependencies
diff --git a/packages/pwa-kit-react-sdk/package.json b/packages/pwa-kit-react-sdk/package.json
index 49d3b19fcf..5e5ddca286 100644
--- a/packages/pwa-kit-react-sdk/package.json
+++ b/packages/pwa-kit-react-sdk/package.json
@@ -3,7 +3,7 @@
"version": "2.7.0-dev",
"description": "A library that supports the isomorphic React rendering pipeline for Commerce Cloud Managed Runtime apps",
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"files": [
diff --git a/packages/pwa-kit-react-sdk/setup-jest.js b/packages/pwa-kit-react-sdk/setup-jest.js
index d3f023ab1b..a485071a2c 100644
--- a/packages/pwa-kit-react-sdk/setup-jest.js
+++ b/packages/pwa-kit-react-sdk/setup-jest.js
@@ -31,7 +31,7 @@ jest.mock('pwa-kit-runtime/utils/ssr-config', () => {
'**/*.json'
],
ssrParameters: {
- ssrFunctionNodeVersion: '14.x',
+ ssrFunctionNodeVersion: '16.x',
proxyConfigs: [
{
host: 'kv7kzm78.api.commercecloud.salesforce.com',
diff --git a/packages/pwa-kit-runtime/CHANGELOG.md b/packages/pwa-kit-runtime/CHANGELOG.md
index bf11dd14c2..d062caef2f 100644
--- a/packages/pwa-kit-runtime/CHANGELOG.md
+++ b/packages/pwa-kit-runtime/CHANGELOG.md
@@ -1,4 +1,6 @@
## v2.7.0-dev (Jan 25, 2023)
+- Support Node 16 [#965](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/965)
+
## v2.6.0 (Jan 25, 2023)
- Security package updates
diff --git a/packages/pwa-kit-runtime/package.json b/packages/pwa-kit-runtime/package.json
index f6a1d96d5f..f233872ce1 100644
--- a/packages/pwa-kit-runtime/package.json
+++ b/packages/pwa-kit-runtime/package.json
@@ -70,7 +70,7 @@
}
},
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"publishConfig": {
diff --git a/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js b/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js
index 44760afdb5..91e33b2098 100644
--- a/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js
+++ b/packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js
@@ -19,7 +19,6 @@ import {
isRemote,
MetricsSender,
outgoingRequestHook,
- PerformanceTimer,
processLambdaResponse,
responseSend,
configureProxyConfigs,
@@ -495,15 +494,11 @@ export const RemoteServerFactory = {
locals.afterResponseCalled = false
locals.responseCaching = {}
- locals.timer = new PerformanceTimer(`req${locals.requestId}`)
locals.originalUrl = req.originalUrl
// Track this response
req.app._requestMonitor._responseStarted(res)
- // Start timing
- locals.timer.start('express-overall')
-
// If the path is /, we enforce that the only methods
// allowed are GET, HEAD or OPTIONS. This is a restriction
// imposed by API Gateway: we enforce it here so that the
@@ -519,8 +514,6 @@ export const RemoteServerFactory = {
const afterResponse = () => {
/* istanbul ignore else */
if (!locals.afterResponseCalled) {
- locals.timer.end('express-overall')
- locals.timingResponse && locals.timer.end('express-response')
locals.afterResponseCalled = true
// Emit timing unless the request is for a proxy
// or bundle path. We don't want to emit metrics
@@ -547,9 +540,6 @@ export const RemoteServerFactory = {
}
req.app.sendMetric(metricName)
}
- locals.timer.finish()
- // Release reference to timer
- locals.timer = null
}
}
@@ -969,16 +959,12 @@ const prepNonProxyRequest = (req, res, next) => {
* @private
*/
const ssrMiddleware = (req, res, next) => {
- const timer = res.locals.timer
- timer.start('ssr-overall')
-
setDefaultHeaders(req, res)
const renderStartTime = Date.now()
const done = () => {
const elapsedRenderTime = Date.now() - renderStartTime
req.app.sendMetric('RenderTime', elapsedRenderTime, 'Milliseconds')
- timer.end('ssr-overall')
}
res.on('finish', done)
diff --git a/packages/pwa-kit-runtime/src/ssr/server/express.js b/packages/pwa-kit-runtime/src/ssr/server/express.js
index c27b8bd891..fe70ace375 100644
--- a/packages/pwa-kit-runtime/src/ssr/server/express.js
+++ b/packages/pwa-kit-runtime/src/ssr/server/express.js
@@ -306,14 +306,10 @@ export const cacheResponseWhenDone = ({
// We know that all the data has been written, so we
// can now store the response in the cache and call
// end() on it.
- const timer = res.locals.timer
req.app.applicationCache._cacheDeletePromise
.then(() => {
localDevLog(`Req ${locals.requestId}: caching response for ${req.url}`)
- timer.start('cache-response')
- return storeResponseInCache(req, res).then(() => {
- timer.end('cache-response')
- })
+ return storeResponseInCache(req, res)
})
.finally(() => {
originalEnd.call(res, callback)
@@ -390,11 +386,7 @@ export const getResponseFromCache = ({req, res, namespace, key}) => {
locals.responseCaching.cacheKey = workingKey
// Return a Promise that handles the asynchronous cache lookup
- const timer = res.locals.timer
- timer.start('check-response-cache')
return req.app.applicationCache.get({key: workingKey, namespace}).then((entry) => {
- timer.end('check-response-cache')
-
localDevLog(
`Req ${locals.requestId}: ${
entry.found ? 'Found' : 'Did not find'
diff --git a/packages/pwa-kit-runtime/src/utils/ssr-server.js b/packages/pwa-kit-runtime/src/utils/ssr-server.js
index 4142492624..b7302194fd 100644
--- a/packages/pwa-kit-runtime/src/utils/ssr-server.js
+++ b/packages/pwa-kit-runtime/src/utils/ssr-server.js
@@ -16,7 +16,6 @@ export * from './ssr-server/detect-device-type'
export * from './ssr-server/metrics-sender'
export * from './ssr-server/outgoing-request-hook'
export * from './ssr-server/parse-end-parameters'
-export * from './ssr-server/performance-timer'
export * from './ssr-server/process-express-response'
export * from './ssr-server/process-lambda-response'
export * from './ssr-server/update-global-agent-options'
diff --git a/packages/pwa-kit-runtime/src/utils/ssr-server.test.js b/packages/pwa-kit-runtime/src/utils/ssr-server.test.js
index 3c15b0d18d..710affd713 100644
--- a/packages/pwa-kit-runtime/src/utils/ssr-server.test.js
+++ b/packages/pwa-kit-runtime/src/utils/ssr-server.test.js
@@ -21,7 +21,6 @@ import {
outgoingRequestHook,
parseCacheControl,
parseEndParameters,
- PerformanceTimer,
processExpressResponse,
processLambdaResponse,
updateGlobalAgentOptions,
@@ -960,41 +959,6 @@ describe('updateGlobalAgentOptions', () => {
})
})
-describe('PerformanceTimer tests', () => {
- let timer
- beforeEach(() => (timer = new PerformanceTimer('')))
- afterEach(() => timer?._observer.disconnect())
-
- test('time() function', () => {
- const func = sinon.stub()
- timer.time('test1', func, 1)
- expect(func.callCount).toBe(1)
- expect(func.calledWith(1)).toBe(true)
-
- func.reset()
- func.throws('Error', 'intentional error')
- expect(() => timer.time('test2', func, 1)).toThrow('intentional error')
- expect(func.callCount).toBe(1)
- })
-
- test('start() and end()', async () => {
- timer.start('test3')
- await new Promise((resolve) => setTimeout(resolve, 10))
- timer.end('test3')
- const summary = timer.summary
- expect(summary.length).toBe(1)
- const entry = summary[0]
- expect(entry.name).toEqual('test3')
- expect(entry.duration).toBeGreaterThanOrEqual(5)
- expect(entry.duration).toBeLessThanOrEqual(30)
- })
-
- test('accessing operationId auto-increments the counter', () => {
- expect(timer.operationId).toBe(1)
- expect(timer.operationId).toBe(2)
- })
-})
-
describe('parseCacheControl', () => {
test('accepts undefined', () => {
const result = parseCacheControl()
diff --git a/packages/pwa-kit-runtime/src/utils/ssr-server/performance-timer.js b/packages/pwa-kit-runtime/src/utils/ssr-server/performance-timer.js
deleted file mode 100644
index 021a36e1b5..0000000000
--- a/packages/pwa-kit-runtime/src/utils/ssr-server/performance-timer.js
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (c) 2022, Salesforce, Inc.
- * All rights reserved.
- * SPDX-License-Identifier: BSD-3-Clause
- * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
- */
-import {PerformanceObserver, performance} from 'perf_hooks'
-
-/**
- * Class that wraps the Node Performance API to guard against
- * changes (it's at Stability 1 in node 8.10) and to make
- * the usage simpler.
- *
- * To use: create an instance of this class, and then call start() and
- * end(), passing the name of the duration being measured. To get all
- * the measured values, use summary().
- *
- * To time a function, use time(), passing a duration name
- * (useful when the same function is called multiple times and you want
- * to measure them separately) and function arguments.
- *
- * @private
- */
-export class PerformanceTimer {
- /**
- * Construct a new PerformanceTimer, with the given name
- * as a 'namespace' within which all durations can be
- * measured. When the object is deleted, it clears all
- * entries under this namespace, so multiple
- * PerformanceTimer instances can be used at once.
- * @private
- * @param name
- */
- constructor(name) {
- this._namespace = `${name}-`
- // Length of the namespace prefix (with the '-' postfix)
- const nslen = this._namespace.length
-
- this._names = {}
- const results = (this._results = [])
- this._observer = new PerformanceObserver((list) => {
- list.getEntries().forEach((entry) => {
- const en = entry.name
- // Only include PerformanceEntry objects in
- // the namespace of this PerformanceTimer
- if (en.startsWith(this._namespace)) {
- results.push({
- name: en.slice(nslen),
- duration: entry.duration
- })
- }
- })
- }, false)
- this._observer.observe({entryTypes: ['measure']})
- this._nextOperationId = 1
- }
-
- /**
- * Returns an operation id that's unique to this PerformanceTimer,
- * so that timing code can use it to distinguish repeat timings.
- * Returns a different value on each access.
- * @return {number}
- */
- get operationId() {
- return this._nextOperationId++
- }
-
- /**
- * Given a name, return a namespaced version of it,
- * with the optional extension, and include teh result
- * in the _names object.
- * @private
- */
- _getMarkName(name, extension) {
- const ext = extension ? `-${extension}` : ''
- const mark = `${this._namespace}${name}${ext}`
- this._names[mark] = true
- return mark
- }
-
- /**
- * Mark the start of the duration with the given name
- * @private
- * @param name {String} duration name
- */
- start(name) {
- performance.mark(this._getMarkName(name, 'start'))
- }
-
- /**
- * Mark the end of the duration with the given name
- * @private
- * @param name {String} duration name
- */
- end(name) {
- const startName = this._getMarkName(name, 'start')
- const endName = this._getMarkName(name, 'end')
- performance.mark(endName)
- performance.measure(this._getMarkName(name), startName, endName)
- performance.clearMarks(startName)
- performance.clearMarks(endName)
- }
-
- /**
- * Clear the duration with the given name
- * @private
- * @param name {String} duration name
- */
- clear(name) {
- performance.clearMarks(this._getMarkName(name, 'start'))
- performance.clearMarks(this._getMarkName(name, 'end'))
- performance.clearMarks(this._getMarkName(name))
- }
-
- /**
- * Finish with this PerformanceObserver
- * @private
- */
- finish() {
- Object.keys(this._names).forEach((mark) => performance.clearMarks(mark))
- this._names = {}
- this._observer.disconnect()
- this._observer = null
- }
-
- /**
- * Measure the duration of the given function,
- * which is called with any remaining arguments
- * after name and fn.
- *
- * If the function throws an error, no duration
- * is recorded.
- *
- * @private
- * @param name {String} duration name
- * @param fn {function} function to call
- * @param args {Array} any arguments to the function
- */
- time(name, fn, ...args) {
- this.start(name)
- try {
- const result = fn(...args)
- this.end(name)
- return result
- } catch (err) {
- this.clear(name)
- throw err
- }
- }
-
- /**
- * Get an object (that can be JSON-serialized) for all
- * measured times.
- * @private
- */
- get summary() {
- return this._results
- }
-
- /**
- * Get access to the performance API used by this timer
- * @private
- * @return {Performance}
- */
- get performance() {
- /* istanbul ignore next */
- return performance
- }
-}
diff --git a/packages/template-express-minimal/package.json b/packages/template-express-minimal/package.json
index b0f23f0a89..ad72f3cb5e 100644
--- a/packages/template-express-minimal/package.json
+++ b/packages/template-express-minimal/package.json
@@ -22,7 +22,7 @@
"mobify": {
"ssrEnabled": true,
"ssrParameters": {
- "ssrFunctionNodeVersion": "14.x"
+ "ssrFunctionNodeVersion": "16.x"
},
"ssrOnly": [
"ssr.js",
diff --git a/packages/template-retail-react-app/README.md b/packages/template-retail-react-app/README.md
index 5f3ad1d072..1b230206eb 100644
--- a/packages/template-retail-react-app/README.md
+++ b/packages/template-retail-react-app/README.md
@@ -6,7 +6,7 @@ Developers don’t have to worry about the underlying infrastructure, whether th
## Requirements
-- Node 14
+- Node 14.17.0 or later
- npm 6.14.4 or later
## Get Started
diff --git a/packages/template-retail-react-app/app/hoc/with-registration/index.test.js b/packages/template-retail-react-app/app/hoc/with-registration/index.test.js
index 266e2a8a69..165cbdb328 100644
--- a/packages/template-retail-react-app/app/hoc/with-registration/index.test.js
+++ b/packages/template-retail-react-app/app/hoc/with-registration/index.test.js
@@ -50,12 +50,12 @@ beforeEach(() => {
afterEach(() => {
jest.resetModules()
+ sessionStorage.clear()
})
test('should execute onClick for registered users', async () => {
const onClick = jest.fn()
-
- renderWithProviders()
+ await renderWithProviders()
await waitFor(() => {
// we wait for login to complete and user's firstName to show up on screen.
@@ -65,24 +65,29 @@ test('should execute onClick for registered users', async () => {
const trigger = screen.getByText(/button/i)
user.click(trigger)
- expect(onClick).toHaveBeenCalledTimes(1)
+ await waitFor(() => {
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
})
-test('should show login modal if user not registered', () => {
+test('should show login modal if user not registered', async () => {
global.server.use(
rest.get('*/customers/:customerId', (req, res, ctx) => {
return res(ctx.delay(0), ctx.status(200), ctx.json(mockedGuestCustomer))
})
)
const onClick = jest.fn()
+ await renderWithProviders()
- renderWithProviders()
-
- const trigger = screen.getByText(/button/i)
- user.click(trigger)
+ const trigger = await screen.findByText(/button/i)
+ await waitFor(() => {
+ user.click(trigger)
+ })
- expect(screen.getByLabelText(/email/i)).toBeInTheDocument()
- expect(screen.getByLabelText(/Password/)).toBeInTheDocument()
- expect(screen.getByText(/forgot password/i)).toBeInTheDocument()
- expect(screen.getByText(/sign in/i)).toBeInTheDocument()
+ await waitFor(() => {
+ expect(screen.getByLabelText(/email/i)).toBeInTheDocument()
+ expect(screen.getByLabelText(/Password/)).toBeInTheDocument()
+ expect(screen.getByText(/forgot password/i)).toBeInTheDocument()
+ expect(screen.getByText(/sign in/i)).toBeInTheDocument()
+ })
})
diff --git a/packages/template-retail-react-app/app/hooks/use-product-view-modal.test.js b/packages/template-retail-react-app/app/hooks/use-product-view-modal.test.js
index 2f68d5b946..6262b70982 100644
--- a/packages/template-retail-react-app/app/hooks/use-product-view-modal.test.js
+++ b/packages/template-retail-react-app/app/hooks/use-product-view-modal.test.js
@@ -8,14 +8,13 @@
import React from 'react'
import {Router} from 'react-router-dom'
import PropTypes from 'prop-types'
-import {screen, render, fireEvent, waitFor} from '@testing-library/react'
+import {screen, fireEvent, waitFor} from '@testing-library/react'
import {createMemoryHistory} from 'history'
import {IntlProvider} from 'react-intl'
import mockProductDetail from '../commerce-api/mocks/variant-750518699578M'
import {useProductViewModal} from './use-product-view-modal'
-import {DEFAULT_LOCALE} from '../utils/test-utils'
-import {renderWithProviders} from '../utils/test-utils'
+import {DEFAULT_LOCALE, renderWithProviders} from '../utils/test-utils'
import messages from '../translations/compiled/en-GB.json'
import {rest} from 'msw'
@@ -84,7 +83,7 @@ describe('useProductViewModal hook', () => {
const history = createMemoryHistory()
history.push('/test/path?color=BLACKFB')
- render(
+ renderWithProviders(
{
const history = createMemoryHistory()
history.push('/test/path')
- render(
+ renderWithProviders(
{
const history = createMemoryHistory()
history.push('/test/path')
- render(
+ renderWithProviders(
diff --git a/packages/template-retail-react-app/app/hooks/use-wishlist.test.js b/packages/template-retail-react-app/app/hooks/use-wishlist.test.js
index 749eb15687..b903640ec6 100644
--- a/packages/template-retail-react-app/app/hooks/use-wishlist.test.js
+++ b/packages/template-retail-react-app/app/hooks/use-wishlist.test.js
@@ -316,13 +316,14 @@ describe('useWishlist hook', () => {
test('createListItem also calls init if not already initialized', () => {
const mock = jest.fn()
const mockFindListByName = jest.fn().mockReturnValue({})
+ const mockInit = jest.fn().mockReturnValue({id: 'test'})
useCustomerProductLists.mockReturnValue({
createListItem: mock,
findListByName: mockFindListByName
})
renderWithProviders()
- result.init = jest.fn()
+ result.init = mockInit
result.createListItem()
diff --git a/packages/template-retail-react-app/app/pages/account/index.test.js b/packages/template-retail-react-app/app/pages/account/index.test.js
index 8543ed1c2b..edb2ebe835 100644
--- a/packages/template-retail-react-app/app/pages/account/index.test.js
+++ b/packages/template-retail-react-app/app/pages/account/index.test.js
@@ -75,7 +75,7 @@ test('Provides navigation for subpages', async () => {
return res(ctx.delay(0), ctx.json(mockOrderHistory))
})
)
- renderWithProviders(, {
+ await renderWithProviders(, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})
expect(await screen.findByTestId('account-page')).toBeInTheDocument()
diff --git a/packages/template-retail-react-app/app/pages/account/orders.test.js b/packages/template-retail-react-app/app/pages/account/orders.test.js
index 8e6b5dfd89..f6ef287a68 100644
--- a/packages/template-retail-react-app/app/pages/account/orders.test.js
+++ b/packages/template-retail-react-app/app/pages/account/orders.test.js
@@ -55,7 +55,7 @@ test('Renders order history and details', async () => {
return res(ctx.delay(0), ctx.json(mockOrderProducts))
})
)
- renderWithProviders(, {
+ await renderWithProviders(, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})
expect(await screen.findByTestId('account-order-history-page')).toBeInTheDocument()
diff --git a/packages/template-retail-react-app/app/pages/registration/index.test.jsx b/packages/template-retail-react-app/app/pages/registration/index.test.jsx
index 404021de99..1416cbfa17 100644
--- a/packages/template-retail-react-app/app/pages/registration/index.test.jsx
+++ b/packages/template-retail-react-app/app/pages/registration/index.test.jsx
@@ -103,12 +103,12 @@ afterEach(() => {
test('Allows customer to create an account', async () => {
// render our test component
- renderWithProviders(, {
+ await renderWithProviders(, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})
// fill out form and submit
- const withinForm = within(screen.getByTestId('sf-auth-modal-form'))
+ const withinForm = within(await screen.findByTestId('sf-auth-modal-form'))
user.paste(withinForm.getByLabelText('First Name'), 'Tester')
user.paste(withinForm.getByLabelText('Last Name'), 'Tester')
@@ -117,8 +117,8 @@ test('Allows customer to create an account', async () => {
user.click(withinForm.getByText(/create account/i))
// wait for success state to appear
+ const myAccount = await screen.findAllByText(/My Account/)
await waitFor(() => {
- screen.logTestingPlaygroundURL()
- expect(screen.getAllByText(/My Account/).length).toEqual(2)
+ expect(myAccount.length).toEqual(2)
})
})
diff --git a/packages/template-retail-react-app/app/pages/reset-password/index.test.jsx b/packages/template-retail-react-app/app/pages/reset-password/index.test.jsx
index 7c29ae0762..a71c8a42e4 100644
--- a/packages/template-retail-react-app/app/pages/reset-password/index.test.jsx
+++ b/packages/template-retail-react-app/app/pages/reset-password/index.test.jsx
@@ -64,11 +64,12 @@ afterEach(() => {
test('Allows customer to go to sign in page', async () => {
// render our test component
- renderWithProviders(, {
+ await renderWithProviders(, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})
- user.click(screen.getByText('Sign in'))
+ user.click(await screen.findByText('Sign in'))
+
await waitFor(() => {
expect(window.location.pathname).toEqual('/uk/en-GB/login')
})
@@ -89,19 +90,24 @@ test('Allows customer to generate password token', async () => {
)
)
// render our test component
- renderWithProviders(, {
+ await renderWithProviders(, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})
// enter credentials and submit
- user.type(screen.getByLabelText('Email'), 'foo@test.com')
- user.click(within(screen.getByTestId('sf-auth-modal-form')).getByText(/reset password/i))
+ user.type(await screen.findByLabelText('Email'), 'foo@test.com')
+ user.click(within(await screen.findByTestId('sf-auth-modal-form')).getByText(/reset password/i))
- // wait for success state
expect(await screen.findByText(/password reset/i, {}, {timeout: 12000})).toBeInTheDocument()
- expect(screen.getByText(/foo@test.com/i)).toBeInTheDocument()
- user.click(screen.getByText('Back to Sign In'))
+ await waitFor(() => {
+ expect(screen.getByText(/foo@test.com/i)).toBeInTheDocument()
+ })
+
+ await waitFor(() => {
+ user.click(screen.getByText('Back to Sign In'))
+ })
+
await waitFor(() => {
expect(window.location.pathname).toEqual('/uk/en-GB/login')
})
@@ -121,10 +127,10 @@ test('Renders error message from server', async () => {
)
)
)
- renderWithProviders()
+ await renderWithProviders()
- user.type(screen.getByLabelText('Email'), 'foo@test.com')
- user.click(within(screen.getByTestId('sf-auth-modal-form')).getByText(/reset password/i))
+ user.type(await screen.findByLabelText('Email'), 'foo@test.com')
+ user.click(within(await screen.findByTestId('sf-auth-modal-form')).getByText(/reset password/i))
expect(await screen.findByText('Something went wrong')).toBeInTheDocument()
})
diff --git a/packages/template-retail-react-app/app/partials/product-view/index.test.js b/packages/template-retail-react-app/app/partials/product-view/index.test.js
index 09b5d09ca5..18bf1a8f3c 100644
--- a/packages/template-retail-react-app/app/partials/product-view/index.test.js
+++ b/packages/template-retail-react-app/app/partials/product-view/index.test.js
@@ -53,11 +53,12 @@ beforeEach(() => {
})
afterEach(() => {
localStorage.clear()
+ sessionStorage.clear()
})
-test('ProductView Component renders properly', () => {
+test('ProductView Component renders properly', async () => {
const addToCart = jest.fn()
- renderWithProviders()
+ await renderWithProviders()
expect(screen.getAllByText(/Black Single Pleat Athletic Fit Wool Suit/i).length).toEqual(2)
expect(screen.getAllByText(/299.99/).length).toEqual(2)
@@ -66,19 +67,24 @@ test('ProductView Component renders properly', () => {
expect(screen.getAllByText(/add to cart/i).length).toEqual(2)
})
-test('ProductView Component renders with addToCart event handler', () => {
+test('ProductView Component renders with addToCart event handler', async () => {
const addToCart = jest.fn()
- renderWithProviders()
+ await renderWithProviders()
const addToCartButton = screen.getAllByText(/add to cart/i)[0]
fireEvent.click(addToCartButton)
- expect(addToCart).toHaveBeenCalledTimes(1)
+
+ await waitFor(() => {
+ expect(addToCart).toHaveBeenCalledTimes(1)
+ })
})
test('ProductView Component renders with addToWishList event handler', async () => {
const addToWishlist = jest.fn()
- renderWithProviders()
+ await renderWithProviders(
+
+ )
await waitFor(() => {
expect(screen.getByText(/customer: registered/)).toBeInTheDocument()
@@ -95,7 +101,7 @@ test('ProductView Component renders with addToWishList event handler', async ()
test('ProductView Component renders with updateWishlist event handler', async () => {
const updateWishlist = jest.fn()
- renderWithProviders(
+ await renderWithProviders(
)
@@ -111,12 +117,23 @@ test('ProductView Component renders with updateWishlist event handler', async ()
})
})
-test('Product View can update quantity', () => {
+test('Product View can update quantity', async () => {
const addToCart = jest.fn()
- renderWithProviders()
- const quantityBox = screen.getByRole('spinbutton')
- expect(quantityBox).toHaveValue('1')
+ await renderWithProviders()
+
+ let quantityBox
+ await waitFor(() => {
+ quantityBox = screen.getByRole('spinbutton')
+ })
+
+ await waitFor(() => {
+ expect(quantityBox).toHaveValue('1')
+ })
+
// update item quantity
userEvent.type(quantityBox, '{backspace}3')
- expect(quantityBox).toHaveValue('3')
+
+ await waitFor(() => {
+ expect(quantityBox).toHaveValue('3')
+ })
})
diff --git a/packages/template-retail-react-app/config/default.js b/packages/template-retail-react-app/config/default.js
index ffdc9181c2..b9c0dd906a 100644
--- a/packages/template-retail-react-app/config/default.js
+++ b/packages/template-retail-react-app/config/default.js
@@ -48,7 +48,7 @@ module.exports = {
'**/*.json'
],
ssrParameters: {
- ssrFunctionNodeVersion: '14.x',
+ ssrFunctionNodeVersion: '16.x',
proxyConfigs: [
{
host: 'kv7kzm78.api.commercecloud.salesforce.com',
diff --git a/packages/template-retail-react-app/config/mocks/default.js b/packages/template-retail-react-app/config/mocks/default.js
index 79c59f2c64..b35c27a1f1 100644
--- a/packages/template-retail-react-app/config/mocks/default.js
+++ b/packages/template-retail-react-app/config/mocks/default.js
@@ -107,7 +107,7 @@ module.exports = {
],
// Additional parameters that configure Express app behavior.
ssrParameters: {
- ssrFunctionNodeVersion: '14.x',
+ ssrFunctionNodeVersion: '16.x',
proxyConfigs: [
{
host: 'localhost:8888',
diff --git a/packages/template-retail-react-app/jest-setup.js b/packages/template-retail-react-app/jest-setup.js
index e675917262..686886cb62 100644
--- a/packages/template-retail-react-app/jest-setup.js
+++ b/packages/template-retail-react-app/jest-setup.js
@@ -99,7 +99,7 @@ jest.mock('./app/commerce-api/utils', () => {
global.TextEncoder = require('util').TextEncoder
// This file consists of global mocks for jsdom.
-class LocalStorageMock {
+class StorageMock {
constructor() {
this.store = {}
}
@@ -117,14 +117,16 @@ class LocalStorageMock {
}
}
-const localStorageMock = new LocalStorageMock()
-
Object.defineProperty(window, 'crypto', {
value: new Crypto()
})
Object.defineProperty(window, 'localStorage', {
- value: localStorageMock
+ value: new StorageMock()
+})
+
+Object.defineProperty(window, 'sessionStorage', {
+ value: new StorageMock()
})
Object.defineProperty(window, 'scrollTo', {
diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json
index 7ed06e8240..879f955a7c 100644
--- a/packages/template-retail-react-app/package.json
+++ b/packages/template-retail-react-app/package.json
@@ -4,7 +4,7 @@
"license": "See license in LICENSE",
"private": true,
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"devDependencies": {
diff --git a/packages/template-typescript-minimal/package.json b/packages/template-typescript-minimal/package.json
index 8f23ddeaf6..11ada7d9f9 100644
--- a/packages/template-typescript-minimal/package.json
+++ b/packages/template-typescript-minimal/package.json
@@ -2,7 +2,7 @@
"name": "typescript-minimal",
"version": "2.7.0-dev",
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"private": true,
@@ -48,7 +48,7 @@
"**/*.json"
],
"ssrParameters": {
- "ssrFunctionNodeVersion": "14.x",
+ "ssrFunctionNodeVersion": "16.x",
"proxyConfigs": [
{
"host": "kv7kzm78.api.commercecloud.salesforce.com",
diff --git a/packages/test-commerce-sdk-react/package.json b/packages/test-commerce-sdk-react/package.json
index bbbd58c018..fe1c5ac3b2 100644
--- a/packages/test-commerce-sdk-react/package.json
+++ b/packages/test-commerce-sdk-react/package.json
@@ -3,7 +3,7 @@
"version": "2.7.0-dev",
"private": true,
"engines": {
- "node": "^14.0.0",
+ "node": "^14.0.0 || ^16.0.0",
"npm": "^6.14.4 || ^7.0.0 || ^8.0.0"
},
"devDependencies": {
@@ -49,7 +49,7 @@
"**/*.json"
],
"ssrParameters": {
- "ssrFunctionNodeVersion": "14.x",
+ "ssrFunctionNodeVersion": "16.x",
"proxyConfigs": [
{
"host": "kv7kzm78.api.commercecloud.salesforce.com",