Skip to content

Monorepo

Purpose

Explain the directory layout, npm workspaces, and how shared packages are actually consumed.

Layout

akshaya-group-global/
├── apps/                  # 7 npm workspaces, one per deployable frontend
│   ├── corporate-web/     # @akshaya/corporate-web
│   ├── software-web/      # @akshaya/software-web
│   ├── logistics-web/     # @akshaya/logistics-web
│   ├── commerce-web/      # @akshaya/commerce-web
│   ├── embroidery-web/    # @akshaya/embroidery-web
│   ├── admin-web/         # @akshaya/admin-web  (does NOT depend on packages/ui or packages/shared)
│   └── investors-web/     # @akshaya/investors-web
├── packages/               # 2 npm workspaces, consumed by apps (not independently deployed)
│   ├── shared/             # @akshaya/shared - framework-agnostic React hooks
│   └── ui/                 # @akshaya/ui - shared components + CSS design system
├── workers/
│   └── api/                # @akshaya/api-worker - the one dedicated API Worker (D1 + R2 + email)
├── infrastructure/
│   └── cloudflare/         # infrastructure/cloudflare/README.md - one-time bootstrap notes
├── scripts/                 # cross-cutting Node/Bash scripts (smoke test, portal-style sync, bootstrap)
└── docs/
    ├── DEPLOYMENT.md        # pre-existing, authoritative deploy/rollback runbook - preserved
    └── engineering/         # this portal

package.json's workspaces field: ["apps/*", "packages/*", "workers/*"] - npm's own workspace mechanism, no separate monorepo tool (no Turborepo/Nx/Lerna).

Shared packages

@akshaya/shared

Framework-agnostic React hooks, consumed by every app except admin-web: useHashRoute (hash-based client routing), useLocalStorage, formatINR (INR currency formatting), uid (short ID generator), useFilteredRows.

@akshaya/ui

Shared components and the design system CSS, also consumed by every app except admin-web: AppShell, Page, Card, Button, Input, Select, TextArea, EmptyState, Status, AkshayaLogoMark, plus the brand constant ({ name: 'Akshaya Group Global', tagline: 'One Group. Multiple Possibilities.' }) and the cross-app customer-portal helpers (customerPortalHref, isSafeReturnPath, CUSTOMER_LOGIN_SOURCES) that back corporate-web's and software-web's own portal routes (see Platform).

admin-web is deliberately isolated from both shared packages - it depends on plain react/react-dom only. Worth knowing before assuming a shared-package change affects every app.

Generated CSS: npm run sync:portal-styles

packages/ui/src/portal-shared.css is the source of truth for the customer/freelancer portal styling; npm run sync:portal-styles (scripts/sync-portal-shared-css.mjs) copies it into apps/corporate-web/public/customer-portal/shared.css and apps/software-web/public/freelancer-portal/shared.css. Never hand-edit those two generated copies - CI's check:portal-styles-drift step re-runs the sync and fails the build if the committed copies don't match (see CI/CD → CI).

Root npm commands - "which command should I use?"

I want to... Command
Install all dependencies npm install
Run Corporate locally npm run dev:corporate
Run Software locally npm run dev:software
Run Logistics locally npm run dev:logistics
Run Commerce locally npm run dev:commerce
Run Embroidery locally npm run dev:embroidery
Run Admin locally npm run dev:admin
Run Investors locally npm run dev:investors
Run the API Worker locally npm run dev:api
Build everything (what CI does) npm run build
Build one app npm run build:<app> (e.g. npm run build:corporate)
Regenerate the shared portal CSS npm run sync:portal-styles
Check the portal CSS hasn't drifted (what CI checks) npm run check:portal-styles-drift
Run all unit tests npm test
Set a Worker secret (Turnstile, Admin bootstrap) npm run configure:turnstile:sitekey / :secret, npm run configure:admin:bootstrap
Apply D1 migrations against production npm run migrate:api:remote (see D1 → REMOTE/MUTATING)
Deploy one app by hand npm run deploy:<app> (e.g. npm run deploy:corporate)
Run the production smoke test npm run smoke:production

Every command above is copied directly from package.json - none invented.