build: 打通 FastAPI 与 Vue 最小工程、依赖与数据库连接
后端入口、配置、Redis 与 Alembic 迁移就位;前端 Vite 工程可安装可构建。
This commit is contained in:
1 parent
ec5835ca77
commit
977783c126
167 files changed
+26082
No files matched your search
@@ -0,0 +1,6 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -0,0 +1,33 @@
|
||||
# API URLs
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_WS_URL=ws://localhost:8000
|
||||
VITE_WIDGET_URL=http://localhost:8000
|
||||
|
||||
# Firebase Configuration
|
||||
VITE_FIREBASE_API_KEY=your_firebase_api_key_here
|
||||
VITE_FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain_here
|
||||
VITE_FIREBASE_PROJECT_ID=your_firebase_project_id_here
|
||||
VITE_FIREBASE_MESSAGING_SENDER_ID=680125690355
|
||||
VITE_FIREBASE_APP_ID=your_firebase_app_id_here
|
||||
VITE_FIREBASE_STORAGE_BUCKET=your_firebase_storage_bucket_here
|
||||
VITE_FIREBASE_MEASUREMENT_ID=your_firebase_measurement_id_here
|
||||
VITE_FIREBASE_VAPID_KEY=your_firebase_vapid_key_here
|
||||
|
||||
# Google Fonts API — OPTIONAL. Only powers the font picker in agent
|
||||
# customization; leave blank and a built-in font list is offered instead.
|
||||
# Must be your own key: Google API keys are HTTP-referrer restricted, so a key
|
||||
# issued for another domain returns 403 on yours. Get one at
|
||||
# https://console.cloud.google.com/apis/credentials (enable "Web Fonts Developer
|
||||
# API"), then restrict it to your app domain.
|
||||
# Leave BLANK rather than a placeholder — a non-empty value is treated as a real
|
||||
# key and the request goes out and fails.
|
||||
VITE_GOOGLE_FONTS_API_KEY=
|
||||
|
||||
# Shopify API Key (for embedded apps) — optional, blank unless you ship one.
|
||||
VITE_SHOPIFY_API_KEY=
|
||||
|
||||
|
||||
|
||||
# Node Environment
|
||||
NODE_ENV=development
|
||||
HOST=0.0.0.0
|
||||
@@ -0,0 +1,38 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
||||
# Module specific ignores
|
||||
src/modules/**/node_modules/
|
||||
src/modules/**/*.log
|
||||
src/modules/**/.DS_Store
|
||||
@@ -0,0 +1,9 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
strict-ssl=false
|
||||
legacy-peer-deps=true
|
||||
audit=false
|
||||
fund=false
|
||||
progress=false
|
||||
save-exact=true
|
||||
always-auth=false
|
||||
access=public
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# frontend
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Run End-to-End Tests with [Playwright](https://playwright.dev)
|
||||
|
||||
```sh
|
||||
# Install browsers for the first run
|
||||
npx playwright install
|
||||
|
||||
# When testing on CI, must build the project first
|
||||
npm run build
|
||||
|
||||
# Runs the end-to-end tests
|
||||
npm run test:e2e
|
||||
# Runs the tests only on Chromium
|
||||
npm run test:e2e -- --project=chromium
|
||||
# Runs the tests of a specific file
|
||||
npm run test:e2e -- tests/example.spec.ts
|
||||
# Runs the tests in debug mode
|
||||
npm run test:e2e -- --debug
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
// Global type declarations
|
||||
declare global {
|
||||
interface Window {
|
||||
APP_CONFIG?: {
|
||||
API_URL?: string
|
||||
WS_URL?: string
|
||||
WIDGET_URL?: string
|
||||
FIREBASE_API_KEY?: string
|
||||
FIREBASE_AUTH_DOMAIN?: string
|
||||
FIREBASE_PROJECT_ID?: string
|
||||
FIREBASE_MESSAGING_SENDER_ID?: string
|
||||
FIREBASE_APP_ID?: string
|
||||
FIREBASE_STORAGE_BUCKET?: string
|
||||
FIREBASE_MEASUREMENT_ID?: string
|
||||
FIREBASE_VAPID_KEY?: string
|
||||
RAZORPAY_KEY_ID?: string
|
||||
GOOGLE_FONTS_API_KEY?: string
|
||||
NODE_ENV?: string
|
||||
HOST?: string
|
||||
DEMO_WIDGET_ID?: string
|
||||
VITE_SHOPIFY_API_KEY?: string
|
||||
GTM_ID?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
export {}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import vueTsEslintConfig from '@vue/eslint-config-typescript'
|
||||
import pluginVitest from '@vitest/eslint-plugin'
|
||||
import pluginPlaywright from 'eslint-plugin-playwright'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
// The enterprise Playwright suite has its own lint config (e2e/eslint.config.js rules).
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', 'src/modules/enterprise/e2e/**'],
|
||||
},
|
||||
|
||||
...pluginVue.configs['flat/essential'],
|
||||
|
||||
// Spread, not wrapped in an object: vueTsEslintConfig() returns an ARRAY of
|
||||
// flat configs. Spreading it into `{}` produced numeric keys ("0", "1", …)
|
||||
// and ESLint refused the whole config with `Unexpected key "0" found`, so
|
||||
// linting has been failing outright rather than reporting anything.
|
||||
...vueTsEslintConfig(),
|
||||
|
||||
{
|
||||
name: 'app/ts-rule-overrides',
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
// Replaces the old `ban-types` entry here, which typescript-eslint v8
|
||||
// split into these successor rules. It was configured to allow bare
|
||||
// `Object`/`Function`; these two are the rules that would flag them.
|
||||
'@typescript-eslint/no-unsafe-function-type': 'off',
|
||||
'@typescript-eslint/no-wrapper-object-types': 'off',
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
// Published images are built once in CI and configured per deployment at
|
||||
// container start, so import.meta.env.VITE_* is undefined at runtime for
|
||||
// every self-hosted install. Config must come from window.APP_CONFIG via
|
||||
// the resolvers in src/config/api.ts (or webclient/widget-env.ts, which
|
||||
// keeps its own fallbacks). Only those resolvers may read the env directly.
|
||||
name: 'app/no-build-time-env',
|
||||
files: ['src/**/*.{ts,mts,tsx,vue}'],
|
||||
ignores: ['src/config/api.ts', 'src/webclient/widget-env.ts', 'src/sw.ts'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
// VITE_* only. Vite's own built-ins (BASE_URL, MODE, DEV, PROD) are
|
||||
// build-time by definition and stay allowed.
|
||||
selector:
|
||||
'MemberExpression[object.object.type="MetaProperty"][object.property.name="env"][property.name=/^VITE_/]',
|
||||
message:
|
||||
'Do not read import.meta.env.VITE_* directly — it is undefined in the published image. Use the runtime resolvers in @/config/api (getApiUrl, resolveUploadUrl, getGoogleFontsApiKey, …).',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
...pluginVitest.configs.recommended,
|
||||
files: ['src/**/__tests__/*'],
|
||||
},
|
||||
|
||||
{
|
||||
...pluginPlaywright.configs['flat/recommended'],
|
||||
files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
||||
},
|
||||
skipFormatting,
|
||||
]
|
||||
Generated
+12970
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"build:dev": "run-p type-check \"build-only:dev {@}\" --",
|
||||
"prebuild:dev": "tsx scripts/generate-dev-env.ts",
|
||||
"build-only:dev": "vite build --mode dev",
|
||||
"build:prod": "run-p type-check \"build-only:prod {@}\" --",
|
||||
"prebuild:prod": "tsx scripts/generate-prod-env.ts",
|
||||
"build-only:prod": "vite build --mode prod",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/",
|
||||
"build:webclient": "npm run build:webclient:prod",
|
||||
"build:webclient:dev": "tsx scripts/build-webclient-dev.ts",
|
||||
"build:webclient:prod": "tsx scripts/build-webclient-prod.ts",
|
||||
"build:widget": "tsx scripts/build-widget.ts",
|
||||
"build:widget:dev": "tsx scripts/build-widget-dev.ts",
|
||||
"build:widget:prod": "tsx scripts/build-widget-prod.ts",
|
||||
"build:widgetdocker": "tsx scripts/build-widget-docker.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "7.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "7.0.0",
|
||||
"@fortawesome/free-brands-svg-icons": "7.0.0",
|
||||
"@fortawesome/free-regular-svg-icons": "7.0.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.0.0",
|
||||
"@fortawesome/vue-fontawesome": "3.1.1",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@shopify/app-bridge": "3.7.10",
|
||||
"@shopify/app-bridge-utils": "3.5.1",
|
||||
"@vue-flow/background": "1.3.2",
|
||||
"@vue-flow/controls": "1.1.2",
|
||||
"@vue-flow/core": "1.45.0",
|
||||
"@vue-flow/minimap": "1.5.3",
|
||||
"apexcharts": "4.4.0",
|
||||
"axios": "1.20.0",
|
||||
"chart.js": "4.4.7",
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "3.4.15",
|
||||
"firebase": "^11.2.0",
|
||||
"floating-vue": "^5.2.2",
|
||||
"marked": "^15.0.6",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.3.0",
|
||||
"primevue": "^4.2.5",
|
||||
"socket.io-client": "4.8.3",
|
||||
"timezone-select-js": "^2.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-advanced-cropper": "^2.8.9",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-sonner": "^1.3.0",
|
||||
"vue3-apexcharts": "1.8.0",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.63.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/webfontloader": "^1.6.38",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vitest/eslint-plugin": "1.1.20",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-playwright": "^2.1.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "^3.3.3",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "6.4.3",
|
||||
"vite-plugin-pwa": "1.3.0",
|
||||
"vite-plugin-vue-devtools": "^7.6.8",
|
||||
"vitest": "3.2.7",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"workbox-core": "7.4.1",
|
||||
"workbox-precaching": "7.4.1",
|
||||
"workbox-routing": "7.4.1",
|
||||
"workbox-strategies": "7.4.1"
|
||||
},
|
||||
"overrides": {
|
||||
"editorconfig": {
|
||||
"minimatch": "^9.0.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { build } from 'esbuild'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import * as dotenv from 'dotenv'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
// Load development environment variables
|
||||
dotenv.config({ path: '.env.dev' })
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
async function buildWebClientDev() {
|
||||
try {
|
||||
console.log('Building webclient with development configuration...')
|
||||
|
||||
// For development, we use the existing config.js which is already set for development
|
||||
// No need to copy files like in production build
|
||||
console.log('Using existing config.js for development build')
|
||||
|
||||
// Set NODE_ENV to development for the build
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
// Baked DEFAULT base URL only; getBaseUrl() still resolves
|
||||
// window.replypierBaseUrl / window.APP_CONFIG at runtime, so no rebuild is
|
||||
// needed when a deployment's env changes.
|
||||
const apiUrl = process.env.VITE_API_URL || 'http://localhost:8000/api/v1'
|
||||
console.log('Using API URL:', apiUrl)
|
||||
|
||||
const sourceFile = resolve(dirname(__dirname), 'src/webclient/replypier.js')
|
||||
|
||||
await build({
|
||||
entryPoints: [sourceFile],
|
||||
bundle: true,
|
||||
minify: false, // Don't minify for development
|
||||
outfile: resolve(dirname(__dirname), 'public/webclient/replypier.min.js'),
|
||||
format: 'iife',
|
||||
target: ['es2015'],
|
||||
loader: {
|
||||
'.svg': 'text',
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"development"',
|
||||
'__REPLYPIER_API_URL__': JSON.stringify(apiUrl),
|
||||
},
|
||||
sourcemap: true, // Add sourcemap for development
|
||||
})
|
||||
|
||||
// Copy output to dist/webclient directory
|
||||
const publicWebclientPath = resolve(dirname(__dirname), 'public/webclient/replypier.min.js')
|
||||
const publicSourcemapPath = resolve(dirname(__dirname), 'public/webclient/replypier.min.js.map')
|
||||
const distWebclientDir = resolve(dirname(__dirname), 'dist/webclient')
|
||||
const distWebclientPath = resolve(distWebclientDir, 'replypier.min.js')
|
||||
const distSourcemapPath = resolve(distWebclientDir, 'replypier.min.js.map')
|
||||
|
||||
// Ensure dist/webclient directory exists
|
||||
if (!fs.existsSync(distWebclientDir)) {
|
||||
fs.mkdirSync(distWebclientDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Copy the built file and sourcemap to dist/webclient
|
||||
fs.copyFileSync(publicWebclientPath, distWebclientPath)
|
||||
if (fs.existsSync(publicSourcemapPath)) {
|
||||
fs.copyFileSync(publicSourcemapPath, distSourcemapPath)
|
||||
}
|
||||
console.log('Development web client copied to dist/webclient/')
|
||||
|
||||
console.log('Development web client built successfully!')
|
||||
} catch (error) {
|
||||
console.error('Development build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWebClientDev()
|
||||
@@ -0,0 +1,69 @@
|
||||
import { build } from 'esbuild'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import * as dotenv from 'dotenv'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
// Load production environment variables
|
||||
dotenv.config({ path: '.env.prod' })
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
async function buildWebClientProd() {
|
||||
try {
|
||||
console.log('Building webclient with production configuration...')
|
||||
|
||||
// Config is now generated by the prebuild step (generate-prod-env.ts)
|
||||
// No need to copy config files here
|
||||
|
||||
// Set NODE_ENV to production for the build
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
// Baked DEFAULT base URL. This only sets the fallback; getBaseUrl() still
|
||||
// resolves window.replypierBaseUrl / window.APP_CONFIG at runtime, so an
|
||||
// env change on a deployment needs no rebuild of this artifact.
|
||||
const apiUrl = process.env.VITE_API_URL || 'https://api.replypier.site/api/v1'
|
||||
console.log('Using API URL:', apiUrl)
|
||||
|
||||
const sourceFile = resolve(dirname(__dirname), 'src/webclient/replypier.js')
|
||||
|
||||
await build({
|
||||
entryPoints: [sourceFile],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
outfile: resolve(dirname(__dirname), 'public/webclient/replypier.min.js'),
|
||||
format: 'iife',
|
||||
target: ['es2015'],
|
||||
loader: {
|
||||
'.svg': 'text',
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'__REPLYPIER_API_URL__': JSON.stringify(apiUrl),
|
||||
},
|
||||
})
|
||||
|
||||
// Copy output to dist/webclient directory
|
||||
const publicWebclientPath = resolve(dirname(__dirname), 'public/webclient/replypier.min.js')
|
||||
const distWebclientDir = resolve(dirname(__dirname), 'dist/webclient')
|
||||
const distWebclientPath = resolve(distWebclientDir, 'replypier.min.js')
|
||||
|
||||
// Ensure dist/webclient directory exists
|
||||
if (!fs.existsSync(distWebclientDir)) {
|
||||
fs.mkdirSync(distWebclientDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Copy the built file to dist/webclient
|
||||
fs.copyFileSync(publicWebclientPath, distWebclientPath)
|
||||
console.log('Production web client copied to dist/webclient/')
|
||||
|
||||
console.log('Production web client built successfully!')
|
||||
} catch (error) {
|
||||
console.error('Production build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWebClientProd()
|
||||
@@ -0,0 +1,67 @@
|
||||
import { build } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
// Load development environment variables
|
||||
dotenv.config({ path: '.env.dev' })
|
||||
|
||||
const config = resolve(process.cwd(), 'vite.widget.config.ts')
|
||||
const assetsDir = resolve(process.cwd(), '../backend/assets')
|
||||
const filesToKeep = ['widget.js', 'widget.css']
|
||||
|
||||
async function buildWidgetDev() {
|
||||
try {
|
||||
console.log('Building widget with development configuration...')
|
||||
|
||||
// Set NODE_ENV to development for the build
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
// Run the Vite build
|
||||
await build({ configFile: config })
|
||||
console.log('Vite build completed')
|
||||
|
||||
// Copy CSS file from unused directory to the root assets directory
|
||||
const unusedDir = path.join(assetsDir, 'unused')
|
||||
if (fs.existsSync(unusedDir)) {
|
||||
const files = fs.readdirSync(unusedDir)
|
||||
const cssFile = files.find(file => file.endsWith('.css'))
|
||||
|
||||
if (cssFile) {
|
||||
const sourcePath = path.join(unusedDir, cssFile)
|
||||
const destPath = path.join(assetsDir, 'widget.css')
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath)
|
||||
console.log(`Copied CSS file to ${destPath}`)
|
||||
}
|
||||
|
||||
// Clean up unused directory
|
||||
fs.rmSync(unusedDir, { recursive: true, force: true })
|
||||
console.log('Cleaned up unused directory')
|
||||
}
|
||||
|
||||
// Clean up the assets directory - keep only widget.js and widget.css
|
||||
const entries = fs.readdirSync(assetsDir)
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!filesToKeep.includes(entry)) {
|
||||
const entryPath = path.join(assetsDir, entry)
|
||||
|
||||
if (fs.statSync(entryPath).isDirectory()) {
|
||||
fs.rmSync(entryPath, { recursive: true, force: true })
|
||||
} else {
|
||||
fs.unlinkSync(entryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Cleaned up assets directory, keeping only: ${filesToKeep.join(', ')}`)
|
||||
console.log('Development widget built successfully!')
|
||||
} catch (error) {
|
||||
console.error('Development build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWidgetDev()
|
||||
@@ -0,0 +1,67 @@
|
||||
import { build } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const config = resolve(process.cwd(), 'vite.widget.docker.config.ts')
|
||||
// For Docker, we need to copy to a shared volume or mounted directory
|
||||
// This assumes the backend assets are mounted/shared between containers
|
||||
const assetsDir = resolve(process.cwd(), '/app/backend-assets')
|
||||
const filesToKeep = ['widget.js', 'widget.css']
|
||||
|
||||
async function buildWidgetDocker() {
|
||||
try {
|
||||
// Ensure the backend assets directory exists
|
||||
if (!fs.existsSync(assetsDir)) {
|
||||
fs.mkdirSync(assetsDir, { recursive: true })
|
||||
console.log(`Created backend assets directory: ${assetsDir}`)
|
||||
}
|
||||
|
||||
// Run the Vite build
|
||||
await build({ configFile: config })
|
||||
console.log('Vite build completed')
|
||||
|
||||
// Copy CSS file from unused directory to the root assets directory
|
||||
const unusedDir = path.join(assetsDir, 'unused')
|
||||
if (fs.existsSync(unusedDir)) {
|
||||
const files = fs.readdirSync(unusedDir)
|
||||
const cssFile = files.find(file => file.endsWith('.css'))
|
||||
|
||||
if (cssFile) {
|
||||
const sourcePath = path.join(unusedDir, cssFile)
|
||||
const destPath = path.join(assetsDir, 'widget.css')
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath)
|
||||
console.log(`Copied CSS file to ${destPath}`)
|
||||
}
|
||||
|
||||
// Clean up unused directory
|
||||
fs.rmSync(unusedDir, { recursive: true, force: true })
|
||||
console.log('Cleaned up unused directory')
|
||||
}
|
||||
|
||||
// Clean up the assets directory - keep only widget.js and widget.css
|
||||
const entries = fs.readdirSync(assetsDir)
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!filesToKeep.includes(entry)) {
|
||||
const entryPath = path.join(assetsDir, entry)
|
||||
|
||||
if (fs.statSync(entryPath).isDirectory()) {
|
||||
fs.rmSync(entryPath, { recursive: true, force: true })
|
||||
} else {
|
||||
fs.unlinkSync(entryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Cleaned up assets directory, keeping only: ${filesToKeep.join(', ')}`)
|
||||
console.log('Widget built successfully for Docker!')
|
||||
console.log(`Files available at: ${assetsDir}`)
|
||||
} catch (error) {
|
||||
console.error('Docker widget build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWidgetDocker()
|
||||
@@ -0,0 +1,67 @@
|
||||
import { build } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// Load production environment variables
|
||||
dotenv.config({ path: '.env.prod' })
|
||||
|
||||
const config = resolve(process.cwd(), 'vite.widget.config.ts')
|
||||
const assetsDir = resolve(process.cwd(), '../backend/assets')
|
||||
const filesToKeep = ['widget.js', 'widget.css']
|
||||
|
||||
async function buildWidgetProd() {
|
||||
try {
|
||||
console.log('Building widget with production configuration...')
|
||||
|
||||
// Set NODE_ENV to production for the build
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
// Run the Vite build
|
||||
await build({ configFile: config })
|
||||
console.log('Vite build completed')
|
||||
|
||||
// Copy CSS file from unused directory to the root assets directory
|
||||
const unusedDir = path.join(assetsDir, 'unused')
|
||||
if (fs.existsSync(unusedDir)) {
|
||||
const files = fs.readdirSync(unusedDir)
|
||||
const cssFile = files.find(file => file.endsWith('.css'))
|
||||
|
||||
if (cssFile) {
|
||||
const sourcePath = path.join(unusedDir, cssFile)
|
||||
const destPath = path.join(assetsDir, 'widget.css')
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath)
|
||||
console.log(`Copied CSS file to ${destPath}`)
|
||||
}
|
||||
|
||||
// Clean up unused directory
|
||||
fs.rmSync(unusedDir, { recursive: true, force: true })
|
||||
console.log('Cleaned up unused directory')
|
||||
}
|
||||
|
||||
// Clean up the assets directory - keep only widget.js and widget.css
|
||||
const entries = fs.readdirSync(assetsDir)
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!filesToKeep.includes(entry)) {
|
||||
const entryPath = path.join(assetsDir, entry)
|
||||
|
||||
if (fs.statSync(entryPath).isDirectory()) {
|
||||
fs.rmSync(entryPath, { recursive: true, force: true })
|
||||
} else {
|
||||
fs.unlinkSync(entryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Cleaned up assets directory, keeping only: ${filesToKeep.join(', ')}`)
|
||||
console.log('Production widget built successfully!')
|
||||
} catch (error) {
|
||||
console.error('Production build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWidgetProd()
|
||||
@@ -0,0 +1,63 @@
|
||||
import { build } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// Load the local .env so a plain `build:widget` bakes the local API URL
|
||||
// (VITE_API_URL) instead of falling through to the widget-env default.
|
||||
dotenv.config({ path: '.env' })
|
||||
|
||||
const config = resolve(process.cwd(), 'vite.widget.config.ts')
|
||||
const assetsDir = resolve(process.cwd(), '../backend/assets')
|
||||
const filesToKeep = ['widget.js', 'widget.css']
|
||||
|
||||
async function buildWidget() {
|
||||
try {
|
||||
// Run the Vite build
|
||||
await build({ configFile: config })
|
||||
console.log('Vite build completed')
|
||||
|
||||
// Copy CSS file from unused directory to the root assets directory
|
||||
const unusedDir = path.join(assetsDir, 'unused')
|
||||
if (fs.existsSync(unusedDir)) {
|
||||
const files = fs.readdirSync(unusedDir)
|
||||
const cssFile = files.find(file => file.endsWith('.css'))
|
||||
|
||||
if (cssFile) {
|
||||
const sourcePath = path.join(unusedDir, cssFile)
|
||||
const destPath = path.join(assetsDir, 'widget.css')
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath)
|
||||
console.log(`Copied CSS file to ${destPath}`)
|
||||
}
|
||||
|
||||
// Clean up unused directory
|
||||
fs.rmSync(unusedDir, { recursive: true, force: true })
|
||||
console.log('Cleaned up unused directory')
|
||||
}
|
||||
|
||||
// Clean up the assets directory - keep only widget.js and widget.css
|
||||
const entries = fs.readdirSync(assetsDir)
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!filesToKeep.includes(entry)) {
|
||||
const entryPath = path.join(assetsDir, entry)
|
||||
|
||||
if (fs.statSync(entryPath).isDirectory()) {
|
||||
fs.rmSync(entryPath, { recursive: true, force: true })
|
||||
} else {
|
||||
fs.unlinkSync(entryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Cleaned up assets directory, keeping only: ${filesToKeep.join(', ')}`)
|
||||
console.log('Widget built successfully!')
|
||||
} catch (error) {
|
||||
console.error('Build failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildWidget()
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
// Load development environment variables
|
||||
dotenv.config({ path: '.env.dev' })
|
||||
|
||||
// (swenv.js is gone — the app service worker src/sw.ts inlines its Firebase
|
||||
// config at build time via import.meta.env)
|
||||
|
||||
// Create config.js content for development using environment variables
|
||||
const configContent = `// Runtime configuration - this file gets replaced at container startup
|
||||
// Assigned to self, not window, so the service worker can importScripts()
|
||||
// it too; in a page self === window, so window.APP_CONFIG still works.
|
||||
self.APP_CONFIG = {
|
||||
// API URLs
|
||||
API_URL: "${process.env.VITE_API_URL || 'http://localhost:8000/api/v1'}",
|
||||
WS_URL: "${process.env.VITE_WS_URL || 'ws://localhost:8000'}",
|
||||
WIDGET_URL: "${process.env.VITE_WIDGET_URL || 'http://localhost:8000'}",
|
||||
|
||||
// Firebase Configuration
|
||||
FIREBASE_API_KEY: "${process.env.VITE_FIREBASE_API_KEY || 'your_firebase_api_key_here'}",
|
||||
FIREBASE_AUTH_DOMAIN: "${process.env.VITE_FIREBASE_AUTH_DOMAIN || 'your_firebase_auth_domain_here'}",
|
||||
FIREBASE_PROJECT_ID: "${process.env.VITE_FIREBASE_PROJECT_ID || 'your_firebase_project_id_here'}",
|
||||
FIREBASE_MESSAGING_SENDER_ID: "${process.env.VITE_FIREBASE_MESSAGING_SENDER_ID || '680125690355'}",
|
||||
FIREBASE_APP_ID: "${process.env.VITE_FIREBASE_APP_ID || 'your_firebase_app_id_here'}",
|
||||
FIREBASE_STORAGE_BUCKET: "${process.env.VITE_FIREBASE_STORAGE_BUCKET || 'your_firebase_storage_bucket_here'}",
|
||||
FIREBASE_MEASUREMENT_ID: "${process.env.VITE_FIREBASE_MEASUREMENT_ID || 'your_firebase_measurement_id_here'}",
|
||||
FIREBASE_VAPID_KEY: "${process.env.VITE_FIREBASE_VAPID_KEY || 'your_firebase_vapid_key_here'}",
|
||||
|
||||
// Google Fonts API
|
||||
GOOGLE_FONTS_API_KEY: "${process.env.VITE_GOOGLE_FONTS_API_KEY || 'your_google_fonts_api_key_here'}",
|
||||
|
||||
// Demo Widget ID
|
||||
DEMO_WIDGET_ID: "${process.env.VITE_DEMO_WIDGET_ID || 'your_demo_widget_id_here'}",
|
||||
|
||||
// Node Environment
|
||||
NODE_ENV: "development",
|
||||
HOST: "${process.env.VITE_HOST || '0.0.0.0'}"
|
||||
};
|
||||
`
|
||||
|
||||
// Write config to both public and dist directories
|
||||
const publicConfigPath = './public/config.js'
|
||||
const distConfigPath = './dist/config.js'
|
||||
|
||||
// Write to public directory
|
||||
fs.writeFileSync(publicConfigPath, configContent)
|
||||
console.log('Development config file generated to public/config.js')
|
||||
|
||||
// Ensure dist directory exists and write to dist directory
|
||||
if (!fs.existsSync('./dist')) {
|
||||
fs.mkdirSync('./dist', { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(distConfigPath, configContent)
|
||||
console.log('Development config file generated to dist/config.js')
|
||||
@@ -0,0 +1,57 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
// Load production environment variables
|
||||
dotenv.config({ path: '.env.prod' })
|
||||
|
||||
// (swenv.js is gone — the app service worker src/sw.ts inlines its Firebase
|
||||
// config at build time via import.meta.env)
|
||||
|
||||
// Create config.js content for production using environment variables
|
||||
const configContent = `// Runtime configuration - this file gets replaced at container startup
|
||||
// Assigned to self, not window, so the service worker can importScripts()
|
||||
// it too; in a page self === window, so window.APP_CONFIG still works.
|
||||
self.APP_CONFIG = {
|
||||
// API URLs
|
||||
API_URL: "${process.env.VITE_API_URL || 'https://api.replypier.site/api/v1'}",
|
||||
WS_URL: "${process.env.VITE_WS_URL || 'wss://api.replypier.site'}",
|
||||
WIDGET_URL: "${process.env.VITE_WIDGET_URL || 'https://api.replypier.site'}",
|
||||
|
||||
// Firebase Configuration
|
||||
FIREBASE_API_KEY: "${process.env.VITE_FIREBASE_API_KEY || ''}",
|
||||
FIREBASE_AUTH_DOMAIN: "${process.env.VITE_FIREBASE_AUTH_DOMAIN || ''}",
|
||||
FIREBASE_PROJECT_ID: "${process.env.VITE_FIREBASE_PROJECT_ID || ''}",
|
||||
FIREBASE_MESSAGING_SENDER_ID: "${process.env.VITE_FIREBASE_MESSAGING_SENDER_ID || ''}",
|
||||
FIREBASE_APP_ID: "${process.env.VITE_FIREBASE_APP_ID || ''}",
|
||||
FIREBASE_STORAGE_BUCKET: "${process.env.VITE_FIREBASE_STORAGE_BUCKET || ''}",
|
||||
FIREBASE_MEASUREMENT_ID: "${process.env.VITE_FIREBASE_MEASUREMENT_ID || ''}",
|
||||
FIREBASE_VAPID_KEY: "${process.env.VITE_FIREBASE_VAPID_KEY || ''}",
|
||||
|
||||
// Google Fonts API
|
||||
GOOGLE_FONTS_API_KEY: "${process.env.VITE_GOOGLE_FONTS_API_KEY || ''}",
|
||||
|
||||
// Demo Widget ID
|
||||
DEMO_WIDGET_ID: "${process.env.VITE_DEMO_WIDGET_ID || ''}",
|
||||
|
||||
// Node Environment
|
||||
NODE_ENV: "production",
|
||||
HOST: "${process.env.VITE_HOST || '0.0.0.0'}",
|
||||
VITE_SHOPIFY_API_KEY: "${process.env.VITE_SHOPIFY_API_KEY || ''}",
|
||||
};
|
||||
`
|
||||
|
||||
// Write config to both public and dist directories
|
||||
const publicConfigPath = './public/config.js'
|
||||
const distConfigPath = './dist/config.js'
|
||||
|
||||
// Write to public directory
|
||||
fs.writeFileSync(publicConfigPath, configContent)
|
||||
console.log('Production config file generated to public/config.js')
|
||||
|
||||
// Ensure dist directory exists and write to dist directory
|
||||
if (!fs.existsSync('./dist')) {
|
||||
fs.mkdirSync('./dist', { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(distConfigPath, configContent)
|
||||
console.log('Production config file generated to dist/config.js')
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*", "src/modules/enterprise/e2e/**"],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.vitest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||
|
||||
"lib": [],
|
||||
"types": ["node", "jsdom"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import { THEME_COLORS } from './src/config/themeColors'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
// Load env file based on `mode` in the current working directory.
|
||||
// When mode is 'prod' or 'dev', load .env.prod or .env.dev respectively
|
||||
let envDir = process.cwd()
|
||||
let envFile = '.env'
|
||||
|
||||
if (mode === 'prod' || mode === 'production') {
|
||||
// Manually load .env.prod for production builds. Plain `vite build`
|
||||
// (mode 'production', used by Dockerfile.frontend.prod) must see the
|
||||
// VITE_FIREBASE_* values too: src/sw.ts inlines them at build time, and
|
||||
// without them the service worker ships with push silently disabled.
|
||||
const envPath = path.resolve(envDir, '.env.prod')
|
||||
if (fs.existsSync(envPath)) {
|
||||
const envConfig = dotenv.parse(fs.readFileSync(envPath))
|
||||
// Merge with process.env
|
||||
for (const key in envConfig) {
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = envConfig[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (mode === 'dev') {
|
||||
// Manually load .env.dev for development builds
|
||||
const envPath = path.resolve(envDir, '.env.dev')
|
||||
if (fs.existsSync(envPath)) {
|
||||
const envConfig = dotenv.parse(fs.readFileSync(envPath))
|
||||
// Merge with process.env
|
||||
for (const key in envConfig) {
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = envConfig[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also load standard Vite env files
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
|
||||
if (command === 'build' && !(process.env.VITE_FIREBASE_API_KEY || env.VITE_FIREBASE_API_KEY)) {
|
||||
console.warn(
|
||||
'\n[pwa] VITE_FIREBASE_API_KEY is not set for this build — the service worker ' +
|
||||
'will ship WITHOUT background push support (foreground notifications still work ' +
|
||||
'via runtime config.js). Provide .env/.env.prod with VITE_FIREBASE_* to enable it.\n'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
// Treat all tags starting with 's-' as custom elements (Polaris web components)
|
||||
isCustomElement: (tag) => tag.startsWith('s-')
|
||||
}
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
// Plugin to inject environment variables into HTML
|
||||
{
|
||||
name: 'html-transform',
|
||||
transformIndexHtml(html) {
|
||||
return html.replace(
|
||||
/<%= VITE_SHOPIFY_API_KEY %>/g,
|
||||
process.env.VITE_SHOPIFY_API_KEY || env.VITE_SHOPIFY_API_KEY || ''
|
||||
)
|
||||
}
|
||||
},
|
||||
VitePWA({
|
||||
strategies: 'injectManifest',
|
||||
srcDir: 'src',
|
||||
filename: 'sw.ts',
|
||||
// Registration is manual (src/pwa/register.ts) so the Shopify App Bridge
|
||||
// script stays the first script tag and embedded contexts never register.
|
||||
injectRegister: false,
|
||||
manifest: {
|
||||
name: 'ReplyPier',
|
||||
short_name: 'ReplyPier',
|
||||
description: 'Agent console for ReplyPier — handle customer chats anywhere.',
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
display: 'standalone',
|
||||
background_color: THEME_COLORS.dark,
|
||||
theme_color: THEME_COLORS.dark,
|
||||
icons: [
|
||||
{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },
|
||||
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },
|
||||
{ src: '/pwa-maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
||||
],
|
||||
},
|
||||
injectManifest: {
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
// config.js is runtime-substituted per environment (docker-entrypoint)
|
||||
// and must never be precached; webclient isn't the agent app.
|
||||
globIgnores: ['config.js', 'config.dev.js', 'webclient/**'],
|
||||
},
|
||||
devOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
headers: {
|
||||
'Service-Worker-Allowed': '/',
|
||||
},
|
||||
// Allow ngrok and other tunnel services for Shopify embedded app development
|
||||
allowedHosts: [
|
||||
'localhost',
|
||||
'.ngrok.app',
|
||||
'.ngrok.io',
|
||||
'frontendchat.ngrok.app',
|
||||
'.replypier.site',
|
||||
],
|
||||
},
|
||||
publicDir: 'public',
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
chunkSizeWarningLimit: 600,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: './index.html',
|
||||
},
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vendor-vue': ['vue', 'vue-router', 'pinia'],
|
||||
'vendor-ui': ['vue3-apexcharts'],
|
||||
'vendor-utils': ['axios', 'marked'],
|
||||
'vendor-firebase': ['firebase/app', 'firebase/messaging', 'firebase/auth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
// Treat all tags starting with 's-' as custom elements (Polaris web components)
|
||||
isCustomElement: (tag) => tag.startsWith('s-')
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/webclient/widget.ts'),
|
||||
name: 'ReplyPierWidget',
|
||||
fileName: 'widget',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: '../backend/assets',
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'es',
|
||||
entryFileNames: 'widget.js',
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.name === 'style.css') {
|
||||
return 'widget.css'
|
||||
}
|
||||
// Place other assets in a subdirectory that we can ignore or clean up later
|
||||
return 'unused/[name]-[hash][extname]'
|
||||
},
|
||||
chunkFileNames: 'unused/[name]-[hash].js',
|
||||
manualChunks: undefined, // Disable code splitting
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
// Treat all tags starting with 's-' as custom elements (Polaris web components)
|
||||
isCustomElement: (tag) => tag.startsWith('s-')
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/webclient/widget.ts'),
|
||||
name: 'ReplyPierWidget',
|
||||
fileName: 'widget',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: '/app/backend-assets',
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'es',
|
||||
entryFileNames: 'widget.js',
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.name === 'style.css') {
|
||||
return 'widget.css'
|
||||
}
|
||||
// Place other assets in a subdirectory that we can ignore or clean up later
|
||||
return 'unused/[name]-[hash][extname]'
|
||||
},
|
||||
chunkFileNames: 'unused/[name]-[hash].js',
|
||||
manualChunks: undefined, // Disable code splitting
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
// vite-plugin-pwa virtual module doesn't exist under vitest
|
||||
'virtual:pwa-register': fileURLToPath(
|
||||
new URL('./src/__tests__/stubs/pwa-register.ts', import.meta.url)
|
||||
),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
// Playwright specs (the enterprise suite lives under its module), not vitest.
|
||||
exclude: [...configDefaults.exclude, 'e2e/**', 'src/modules/enterprise/e2e/**'],
|
||||
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user