From c22265a3372117cf85d3176ba59cd0563f24490c Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Wed, 27 Mar 2024 11:41:44 -0600 Subject: [PATCH 1/8] #175: copy two workflow files to be added to main --- .github/workflows/release-toatu.yml | 34 +++++++++++++++++++++++++++++ amplify.yml | 18 +++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .github/workflows/release-toatu.yml create mode 100644 amplify.yml diff --git a/.github/workflows/release-toatu.yml b/.github/workflows/release-toatu.yml new file mode 100644 index 00000000000..616af89aad6 --- /dev/null +++ b/.github/workflows/release-toatu.yml @@ -0,0 +1,34 @@ +name: Production Release to https://zhao.toatu.com via CloudFront/S3 +on: + workflow_dispatch + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Check out the repository + - name: Checkout + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-southeast-1 + + # Run docker-compose build + - name: Build Client Package + run: | + npm ci + npm run frontend + + # Tag it properly before push to github + - name: Sync to S3 + run: | + aws s3 sync client/dist s3://librechat-aitok-web/zhao.toatu.com --region ap-southeast-1 --delete + + - name: Invalidate CloudFront Cache + run: | + aws cloudfront create-invalidation --distribution-id EEOZSCDOFHL2G --paths "/*" + diff --git a/amplify.yml b/amplify.yml new file mode 100644 index 00000000000..e774af14b82 --- /dev/null +++ b/amplify.yml @@ -0,0 +1,18 @@ +version: 1 +frontend: + phases: + preBuild: + commands: + - npm ci + - npm run frontend:ci # use frontend:ci for development mode + # IMPORTANT - Please verify your build commands + build: + commands: [] + artifacts: + baseDirectory: client/dist + files: + - '**/*' + cache: + paths: + - node_modules/**/* + - client/node_modules/**/* From 2d4f26b1ed1c0fa883592174e0ae2b65ccbd2ea6 Mon Sep 17 00:00:00 2001 From: yinglj Date: Sun, 31 Mar 2024 10:45:26 +0800 Subject: [PATCH 2/8] Enhancement: [zhao.toatu.com] change the billing link for main branch --- api/server/routes/ask/openAI.js | 3 ++- api/server/routes/config.js | 2 ++ client/src/components/Profile/index.tsx | 26 +++++++++++++++++++++-- client/src/localization/languages/Eng.tsx | 2 ++ client/src/localization/languages/Zh.tsx | 2 ++ packages/data-provider/src/types.ts | 2 ++ 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index 0eec1496234..4c55ad3d072 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -128,9 +128,10 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, ], }); let dailyQuota = quota[endpointOption.modelOptions.model].toFixed(0); + let portalWebsiteURL = process.env.PORTAL_WEBSITE_URL; if (messagesCount >= dailyQuota) { throw new Error( - `超出了您的使用额度(${endpointOption.modelOptions.model}模型每天${dailyQuota}条消息)。由于需要支付越来越多、每月上万元的API费用,如果您经常使用我们的服务,请通过此网页购买更多额度、支持我们持续提供GPT服务:https://iaitok.com 目前限时半价优惠每月60元`, + `超出了您的使用额度(${endpointOption.modelOptions.model}模型每天${dailyQuota}条消息)。由于需要支付越来越多、每月上万元的API费用,如果您经常使用我们的服务,请打开“我的主页”进行购买,支持我们持续提供GPT服务。更多优惠套餐,请前往网站订购:${portalWebsiteURL}`, ); } } diff --git a/api/server/routes/config.js b/api/server/routes/config.js index e9f55d97f32..2823954214d 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -21,6 +21,8 @@ router.get('/', async function (req, res) { serverDomain: process.env.DOMAIN_SERVER || 'http://localhost:3080', registrationEnabled: isEnabled(process.env.ALLOW_REGISTRATION), socialLoginEnabled: isEnabled(process.env.ALLOW_SOCIAL_LOGIN), + proMemberPaymentURL: process.env.PRO_MEMBER_PAYMENT_URL, + portalWebsiteURL: process.env.PORTAL_WEBSITE_URL, emailEnabled: (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) && !!process.env.EMAIL_USERNAME && diff --git a/client/src/components/Profile/index.tsx b/client/src/components/Profile/index.tsx index 7d345814f70..3bb24024c5c 100644 --- a/client/src/components/Profile/index.tsx +++ b/client/src/components/Profile/index.tsx @@ -3,7 +3,12 @@ import { Tabs, TabsList, TabsTrigger } from '../ui/Tabs'; import { cn } from '~/utils'; import useDocumentTitle from '~/hooks/useDocumentTitle'; import { useNavigate, useParams } from 'react-router-dom'; -import { TUser, useFollowUserMutation, useGetUserByIdQuery } from 'librechat-data-provider'; +import { + TUser, + useFollowUserMutation, + useGetStartupConfig, + useGetUserByIdQuery, +} from 'librechat-data-provider'; import { useAuthContext } from '~/hooks/AuthContext'; import LikedConversations from './LikedConversation'; import { useRecoilValue } from 'recoil'; @@ -39,6 +44,7 @@ function ProfileContent() { const getUserByIdQuery = useGetUserByIdQuery(userId); const followUserMutation = useFollowUserMutation(); + const { data: startupConfig } = useGetStartupConfig(); const defaultClasses = 'p-2 rounded-md min-w-[75px] font-normal text-xs'; const defaultSelected = cn( @@ -460,11 +466,27 @@ function ProfileContent() {
{localize('com_ui_pro_member_expired_at')}: {proMemberExpiredAt.getFullYear()}- {proMemberExpiredAt.getMonth() + 1}-{proMemberExpiredAt.getDate()} +
) : (
-
{localize('com_ui_free_member')}
+
+ {localize('com_ui_free_member')} + +
) ) : ( diff --git a/client/src/localization/languages/Eng.tsx b/client/src/localization/languages/Eng.tsx index e5e17e436fe..9017c52d5eb 100644 --- a/client/src/localization/languages/Eng.tsx +++ b/client/src/localization/languages/Eng.tsx @@ -12,6 +12,8 @@ export default { com_ui_followers: 'Followers', com_ui_following: 'Following', com_ui_pro_member_expired_at: 'Pro Member. Expired On', + com_ui_become_pro_member: 'become pro member', + com_ui_renewal_pro_member: 'renewal pro member', com_ui_free_member: 'Free Member', com_ui_about_yourself: 'About Yourself', com_ui_profession: 'Profession', diff --git a/client/src/localization/languages/Zh.tsx b/client/src/localization/languages/Zh.tsx index 40a9da49c9b..e2bef78d5d2 100644 --- a/client/src/localization/languages/Zh.tsx +++ b/client/src/localization/languages/Zh.tsx @@ -25,6 +25,8 @@ export default { com_ui_followers: '粉丝', com_ui_following: '关注', com_ui_pro_member_expired_at: 'Pro用户. 过期日期', + com_ui_become_pro_member: '成为专业会员', + com_ui_renewal_pro_member: '续期专业会员', com_ui_free_member: '免费用户', com_ui_about_yourself: '关于自己', com_ui_profession: '职业', diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index ab758459cea..5eede8302aa 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -191,6 +191,8 @@ export type TStartupConfig = { emailEnabled: boolean; checkBalance: boolean; customFooter?: string; + proMemberPaymentURL: string; + portalWebsiteURL: string; }; export type TRefreshTokenResponse = { From 61d88c56b90f8606f643702253933c1d8ec33d89 Mon Sep 17 00:00:00 2001 From: yinglj Date: Sun, 31 Mar 2024 10:53:29 +0800 Subject: [PATCH 3/8] fix ESLint found 6 errors of AzureAiSearch.js --- api/app/clients/tools/AzureAiSearch.js | 52 ++++++++++++++----- .../clients/tools/structured/AzureAISearch.js | 52 ++++++++++++++----- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/api/app/clients/tools/AzureAiSearch.js b/api/app/clients/tools/AzureAiSearch.js index 7a5742b1757..29111029675 100644 --- a/api/app/clients/tools/AzureAiSearch.js +++ b/api/app/clients/tools/AzureAiSearch.js @@ -16,39 +16,67 @@ class AzureAISearch extends Tool { const getValue = (fieldNames, defaultValue) => { for (const name of fieldNames) { const value = fields[name] || process.env[name]; - if (value !== undefined && value !== null) return value; + if (value !== undefined && value !== null) { + return value; + } } return defaultValue; }; - this.serviceEndpoint = getValue(['AZURE_AI_SEARCH_SERVICE_ENDPOINT', 'AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT'], this.getServiceEndpoint()); - this.indexName = getValue(['AZURE_AI_SEARCH_INDEX_NAME', 'AZURE_COGNITIVE_SEARCH_INDEX_NAME'], this.getIndexName()); - this.apiKey = getValue(['AZURE_AI_SEARCH_API_KEY', 'AZURE_COGNITIVE_SEARCH_API_KEY'], this.getApiKey()); - this.apiVersion = getValue(['AZURE_AI_SEARCH_API_VERSION', 'AZURE_COGNITIVE_SEARCH_API_VERSION'], AzureAISearch.DEFAULT_API_VERSION); - this.queryType = getValue(['AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE'], AzureAISearch.DEFAULT_QUERY_TYPE); - this.top = getValue(['AZURE_AI_SEARCH_SEARCH_OPTION_TOP', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP'], AzureAISearch.DEFAULT_TOP); + this.serviceEndpoint = getValue( + ['AZURE_AI_SEARCH_SERVICE_ENDPOINT', 'AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT'], + this.getServiceEndpoint(), + ); + this.indexName = getValue( + ['AZURE_AI_SEARCH_INDEX_NAME', 'AZURE_COGNITIVE_SEARCH_INDEX_NAME'], + this.getIndexName(), + ); + this.apiKey = getValue( + ['AZURE_AI_SEARCH_API_KEY', 'AZURE_COGNITIVE_SEARCH_API_KEY'], + this.getApiKey(), + ); + this.apiVersion = getValue( + ['AZURE_AI_SEARCH_API_VERSION', 'AZURE_COGNITIVE_SEARCH_API_VERSION'], + AzureAISearch.DEFAULT_API_VERSION, + ); + this.queryType = getValue( + [ + 'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', + 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE', + ], + AzureAISearch.DEFAULT_QUERY_TYPE, + ); + this.top = getValue( + ['AZURE_AI_SEARCH_SEARCH_OPTION_TOP', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP'], + AzureAISearch.DEFAULT_TOP, + ); this.select = this.getSelect(); } initializeClient() { - this.client = new SearchClient(this.serviceEndpoint, this.indexName, new AzureKeyCredential(this.apiKey), { apiVersion: this.apiVersion }); + // eslint-disable-next-line max-len + this.client = new SearchClient( + this.serviceEndpoint, + this.indexName, + new AzureKeyCredential(this.apiKey), + { apiVersion: this.apiVersion }, + ); } name = 'azure-ai-search'; - description = - 'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input'; + description = 'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input'; async _call(query) { try { const searchOptions = { queryType: this.queryType, top: this.top, - select: this.select + select: this.select, }; const searchResults = await this.client.search(query, searchOptions); - return JSON.stringify(searchResults.results.map(result => result.document)); + return JSON.stringify(searchResults.results.map((result) => result.document)); } catch (error) { console.error(`Azure AI Search request failed: ${error}`); return 'There was an error with Azure AI Search.'; diff --git a/api/app/clients/tools/structured/AzureAISearch.js b/api/app/clients/tools/structured/AzureAISearch.js index 8d22ced0b13..f2e005b7c2f 100644 --- a/api/app/clients/tools/structured/AzureAISearch.js +++ b/api/app/clients/tools/structured/AzureAISearch.js @@ -18,22 +18,51 @@ class AzureAISearch extends StructuredTool { const getValue = (fieldNames, defaultValue) => { for (const name of fieldNames) { const value = fields[name] || process.env[name]; - if (value !== undefined && value !== null) return value; + if (value !== undefined && value !== null) { + return value; + } } return defaultValue; }; - this.serviceEndpoint = getValue(['AZURE_AI_SEARCH_SERVICE_ENDPOINT', 'AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT'], this.getServiceEndpoint()); - this.indexName = getValue(['AZURE_AI_SEARCH_INDEX_NAME', 'AZURE_COGNITIVE_SEARCH_INDEX_NAME'], this.getIndexName()); - this.apiKey = getValue(['AZURE_AI_SEARCH_API_KEY', 'AZURE_COGNITIVE_SEARCH_API_KEY'], this.getApiKey()); - this.apiVersion = getValue(['AZURE_AI_SEARCH_API_VERSION', 'AZURE_COGNITIVE_SEARCH_API_VERSION'], AzureAISearch.DEFAULT_API_VERSION); - this.queryType = getValue(['AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE'], AzureAISearch.DEFAULT_QUERY_TYPE); - this.top = getValue(['AZURE_AI_SEARCH_SEARCH_OPTION_TOP', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP'], AzureAISearch.DEFAULT_TOP); + this.serviceEndpoint = getValue( + ['AZURE_AI_SEARCH_SERVICE_ENDPOINT', 'AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT'], + this.getServiceEndpoint(), + ); + this.indexName = getValue( + ['AZURE_AI_SEARCH_INDEX_NAME', 'AZURE_COGNITIVE_SEARCH_INDEX_NAME'], + this.getIndexName(), + ); + this.apiKey = getValue( + ['AZURE_AI_SEARCH_API_KEY', 'AZURE_COGNITIVE_SEARCH_API_KEY'], + this.getApiKey(), + ); + this.apiVersion = getValue( + ['AZURE_AI_SEARCH_API_VERSION', 'AZURE_COGNITIVE_SEARCH_API_VERSION'], + AzureAISearch.DEFAULT_API_VERSION, + ); + this.queryType = getValue( + [ + 'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE', + 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE', + ], + AzureAISearch.DEFAULT_QUERY_TYPE, + ); + this.top = getValue( + ['AZURE_AI_SEARCH_SEARCH_OPTION_TOP', 'AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP'], + AzureAISearch.DEFAULT_TOP, + ); this.select = this.getSelect(); } initializeClient() { - this.client = new SearchClient(this.serviceEndpoint, this.indexName, new AzureKeyCredential(this.apiKey), { apiVersion: this.apiVersion }); + // eslint-disable-next-line max-len + this.client = new SearchClient( + this.serviceEndpoint, + this.indexName, + new AzureKeyCredential(this.apiKey), + { apiVersion: this.apiVersion }, + ); } initializeSchema() { @@ -44,8 +73,7 @@ class AzureAISearch extends StructuredTool { name = 'azure-ai-search'; - description = - 'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input'; + description = 'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input'; async _call(data) { const { query } = data; @@ -53,11 +81,11 @@ class AzureAISearch extends StructuredTool { const searchOptions = { queryType: this.queryType, top: this.top, - select: this.select + select: this.select, }; const searchResults = await this.client.search(query, searchOptions); - return JSON.stringify(searchResults.results.map(result => result.document)); + return JSON.stringify(searchResults.results.map((result) => result.document)); } catch (error) { console.error(`Azure AI Search request failed: ${error}`); return 'There was an error with Azure AI Search.'; From 0fad35406540bf2040af041b69f395eea11efe3f Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 31 Mar 2024 18:29:08 -0600 Subject: [PATCH 4/8] Remove the use portalWebsiteURL --- api/server/routes/ask/openAI.js | 3 +-- api/server/routes/config.js | 1 - packages/data-provider/src/types.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index 4c55ad3d072..e4309bd86fe 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -128,10 +128,9 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, ], }); let dailyQuota = quota[endpointOption.modelOptions.model].toFixed(0); - let portalWebsiteURL = process.env.PORTAL_WEBSITE_URL; if (messagesCount >= dailyQuota) { throw new Error( - `超出了您的使用额度(${endpointOption.modelOptions.model}模型每天${dailyQuota}条消息)。由于需要支付越来越多、每月上万元的API费用,如果您经常使用我们的服务,请打开“我的主页”进行购买,支持我们持续提供GPT服务。更多优惠套餐,请前往网站订购:${portalWebsiteURL}`, + `超出了您的使用额度(${endpointOption.modelOptions.model}模型每天${dailyQuota}条消息)。由于需要支付越来越多、每月上万元的API费用,如果您经常使用我们的服务,请打开“我的主页”进行购买,支持我们持续提供GPT服务。`, ); } } diff --git a/api/server/routes/config.js b/api/server/routes/config.js index 2823954214d..cc2b6c7e113 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -22,7 +22,6 @@ router.get('/', async function (req, res) { registrationEnabled: isEnabled(process.env.ALLOW_REGISTRATION), socialLoginEnabled: isEnabled(process.env.ALLOW_SOCIAL_LOGIN), proMemberPaymentURL: process.env.PRO_MEMBER_PAYMENT_URL, - portalWebsiteURL: process.env.PORTAL_WEBSITE_URL, emailEnabled: (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) && !!process.env.EMAIL_USERNAME && diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 5eede8302aa..fd6c174e7da 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -192,7 +192,6 @@ export type TStartupConfig = { checkBalance: boolean; customFooter?: string; proMemberPaymentURL: string; - portalWebsiteURL: string; }; export type TRefreshTokenResponse = { From fc7dccbeb502bca2cb3fcd48400c3e209252f506 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 31 Mar 2024 18:42:18 -0600 Subject: [PATCH 5/8] Add to TODO comment --- api/server/routes/ask/openAI.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index e4309bd86fe..50636f58417 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -129,6 +129,7 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, }); let dailyQuota = quota[endpointOption.modelOptions.model].toFixed(0); if (messagesCount >= dailyQuota) { + // TODO: this error message should be implemented by the client based on locale throw new Error( `超出了您的使用额度(${endpointOption.modelOptions.model}模型每天${dailyQuota}条消息)。由于需要支付越来越多、每月上万元的API费用,如果您经常使用我们的服务,请打开“我的主页”进行购买,支持我们持续提供GPT服务。`, ); From cb62dbf335614ea8afa032f632d895c8366a9818 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 31 Mar 2024 19:11:48 -0600 Subject: [PATCH 6/8] automatically fill payment email and locale but the language is not working --- client/src/components/Profile/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/components/Profile/index.tsx b/client/src/components/Profile/index.tsx index 3bb24024c5c..46b76fe605c 100644 --- a/client/src/components/Profile/index.tsx +++ b/client/src/components/Profile/index.tsx @@ -469,7 +469,11 @@ function ProfileContent() { @@ -482,7 +486,11 @@ function ProfileContent() { From 8378267d8fc528dd14790e0100e45d8b1233ceda Mon Sep 17 00:00:00 2001 From: yinglj Date: Mon, 1 Apr 2024 10:28:12 +0800 Subject: [PATCH 7/8] Fix the locale of stripe payment page --- client/src/components/Profile/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/components/Profile/index.tsx b/client/src/components/Profile/index.tsx index 46b76fe605c..668533cf704 100644 --- a/client/src/components/Profile/index.tsx +++ b/client/src/components/Profile/index.tsx @@ -39,6 +39,8 @@ function ProfileContent() { const { userId = '' } = useParams(); const { user, token } = useAuthContext(); const localize = useLocalize(); + let lang = localStorage.getItem('lang'); + lang = lang ? lang.substring(0, 2) : 'en'; const navigate = useNavigate(); useDocumentTitle('Profile'); @@ -471,7 +473,7 @@ function ProfileContent() { className="rounded bg-green-500 px-4 py-1 text-white hover:bg-green-600" onClick={() => window.open( - `${startupConfig?.proMemberPaymentURL}?locale=${navigator.language}&prefilled_email=${profileUser?.email}`, + `${startupConfig?.proMemberPaymentURL}?locale=${lang}&prefilled_email=${profileUser?.email}`, ) } > @@ -488,7 +490,7 @@ function ProfileContent() { className="rounded bg-green-500 px-4 py-1 text-white hover:bg-green-600" onClick={() => window.open( - `${startupConfig?.proMemberPaymentURL}?locale=${navigator.language}&prefilled_email=${profileUser?.email}`, + `${startupConfig?.proMemberPaymentURL}?locale=${lang}&prefilled_email=${profileUser?.email}`, ) } > From aa083e30ef079ea89425d0739ba426923614548c Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Wed, 27 Mar 2024 11:50:07 -0600 Subject: [PATCH 8/8] #175: optimize amplify.yml --- amplify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amplify.yml b/amplify.yml index e774af14b82..4de81009d6c 100644 --- a/amplify.yml +++ b/amplify.yml @@ -4,10 +4,10 @@ frontend: preBuild: commands: - npm ci - - npm run frontend:ci # use frontend:ci for development mode # IMPORTANT - Please verify your build commands build: - commands: [] + commands: + - npm run frontend:ci # use frontend:ci for development mode artifacts: baseDirectory: client/dist files: