PVR Tech Studio
Dev architecture

Architecture & File Structure

The monorepo layout, the three editions plus the shared design-system and mock API, the build pipeline, and how it all deploys.

3 min read
Updated June 21, 2026

This guide is for developers integrating or extending Taxi CRM 2026. The product is an npm-workspaces monorepo with three UI editions that share one design layer and one mock API. The HTML edition is the canonical source; the React and Vue editions consume a compiled @taxi-crm/design-system package built from it.

Monorepo layout

taxi-crm-2024/                         (root workspace)
├── apps/
│   ├── html/                          canonical edition — Gulp + PUG + LESS → static HTML
│   │   ├── pug/                        page templates + partials
│   │   └── assets/
│   │       ├── css/                    ~70 LESS partials; style.less is the entry point
│   │       │   ├── style.less          imports every partial
│   │       │   └── 01-variable.less    design tokens (@-variables)
│   │       ├── js/
│   │       │   ├── core/app.js         loaded on every page (app.*, API_CONFIG, i18n)
│   │       │   └── pages/<feature>/    one script per page
│   │       ├── locales/                8-language translation JSON
│   │       └── img/
│   ├── react/                         React 18 + Vite 5 edition
│   │   └── src/ (main.jsx, App.jsx)
│   └── vue/                           Vue 3 + Vite 5 edition
│       └── src/ (main.js, App.vue)
├── packages/
│   └── design-system/                 @taxi-crm/design-system (framework-agnostic)
│       ├── api-contract.mjs           endpoints + createApi() helper
│       ├── build.mjs                  builds dist/ from apps/html
│       └── dist/                       (gitignored) style.css, tokens.*, img/, locales/
├── services/
│   └── api/                           Express 4 mock API (Node 20)
│       ├── server.js                  Cloud Run entrypoint
│       ├── index.js                   the Express app
│       └── src/ (routes, controllers, config.js, middlewares)
├── tools/
│   └── ship.mjs                       packaging tool (PUG/LESS → dist, ThemeForest scrub)
├── firebase.json                      Hosting sites + Cloud Run rewrite
└── package.json                       workspaces + root scripts

The workspaces are declared in the root package.json as apps/*, services/* and packages/*.

The three editions

EditionStackHow it buildsData layer
HTML (canonical)Gulp 4, PUG → HTML, LESS → CSS, jQuery 3.7 + Bootstrap 5 + pluginstools/ship.mjs (PUG/LESS compile)API_CONFIG in app.js
ReactReact 18, Vite 5vite build@taxi-crm/design-system/api-contract
VueVue 3, Vite 5vite build@taxi-crm/design-system/api-contract

The HTML edition holds the authoritative LESS, locales and image assets. The React and Vue editions don't re-implement the styling — they import the compiled outputs of the design-system package (see below), so all three editions stay visually identical.

The shared design-system package

@taxi-crm/design-system is a framework-agnostic layer consumed by every edition. Its build.mjs (run with npm run ds:build) builds dist/ from apps/html:

OutputHow it's produced
dist/style.cssLESS style.less compiled with the less package
dist/tokens.jsonthe flat @name: value; variables extracted from 01-variable.less
dist/tokens.cssthe same tokens emitted as :root { --name: value; } custom properties
dist/img/, dist/locales/copied from apps/html/assets (8 languages)

The package's exports map exposes these to the Vite editions:

"exports": {
  "./api-contract": "./api-contract.mjs",
  "./style.css":    "./dist/style.css",
  "./tokens.css":   "./dist/tokens.css",
  "./tokens.json":  "./dist/tokens.json"
}

A React/Vue entry file imports them directly:

import '@taxi-crm/design-system/style.css';
import '@taxi-crm/design-system/tokens.css';
import { createApi, endpoints } from '@taxi-crm/design-system/api-contract';

Because the CSS, tokens and locales all originate from the HTML edition, editing 01-variable.less and re-running ds:build re-themes all three editions at once.

The mock API service

services/api is an Express 4 app (Node 20) that returns static JSON fixtures so the editions have data to render. Entry point server.js binds the app from index.js to the port Cloud Run provides (defaulting to 8080 in development). It is a reference implementation — read it to learn the exact response shape your real backend must return, then replace it. Full details in Data & API Integration.

Build pipeline

apps/html (PUG + LESS)  ──ship.mjs──▶  dist/public/taxi-crm/html  (static HTML site)
apps/html/assets/css    ──build.mjs─▶  packages/design-system/dist (style.css + tokens)
apps/react, apps/vue    ──vite build─▶  per-edition dist/ (consume the design-system)
services/api            ──ship.mjs──▶  dist/api  (the Express app, minus node_modules)
Command (run from root)What it does
npm run ds:buildBuild the design-system dist/ from apps/html.
npm run ship:htmlCompile the HTML edition (PUG → HTML, LESS → CSS) into dist/public.
npm run ship:apiCopy the Express app into dist/api.
npm run ship:themeforestBuild a scrubbed, license-clean ThemeForest package + zip.
npm run dev:apiStart the mock API locally (npm start in services/api).

The HTML build is driven by tools/ship.mjs: it copies assets/ (stripping .less/.map), compiles style.lessstyle.css with the less package, and renders every top-level pug/*.pug to .html with pug. The themeforest mode additionally swaps demo photos for placeholders and strips account-tied references (live API URL, Maps key, Font Awesome Kit).

Deployment

Production is Firebase Hosting (static site) plus Google Cloud Run (the API), configured in firebase.json:

  • The main Hosting site serves the static HTML from dist/public, and redirects //taxi-crm/html/.
  • A second Hosting site rewrites all requests to the Cloud Run service taxi-crm-api in region asia-south1, so the API is reached same-origin under /api.
  • The mock API deploys with npm run deploy:api (gcloud run deploy taxi-crm-api … --region asia-south1); the site deploys with npm run deploy:html (firebase deploy --only hosting). npm run deploy runs ship + both.

In development the React/Vue Vite dev servers proxy /apihttp://localhost:8080 (the local Express app), mirroring the production same-origin rewrite. See Data & API Integration for the request/response contract.

Where to go next

Was this page helpful?