@pradip1995/create-storefront-app 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/bin/create-storefront-app.js +141 -59
  2. package/lib/build-backend-package.js +54 -0
  3. package/lib/build-homepage-defaults.cjs +50 -0
  4. package/lib/deps.js +3 -1
  5. package/lib/docker-template-vars.js +32 -0
  6. package/lib/medusa-plugin-versions.json +55 -0
  7. package/lib/scaffold-backend.js +107 -0
  8. package/lib/scaffold-docker.js +82 -0
  9. package/lib/versions.js +4 -3
  10. package/package.json +3 -2
  11. package/templates/backend/.cursor/commands/seed-backend.md +16 -0
  12. package/templates/backend/.cursor/rules/medusa-backend.mdc +55 -0
  13. package/templates/backend/config/README.md +21 -0
  14. package/templates/backend/config/homepage-config.json +568 -0
  15. package/templates/backend/env.template +53 -0
  16. package/templates/backend/gitignore +8 -0
  17. package/templates/backend/medusa-config.full.ts +251 -0
  18. package/templates/backend/medusa-config.minimal.ts +141 -0
  19. package/templates/backend/scripts/build-dynamic-config-defaults.mjs +25 -0
  20. package/templates/backend/scripts/build-homepage-defaults.cjs +50 -0
  21. package/templates/backend/src/config/product-metadata-descriptors.ts +27 -0
  22. package/templates/backend/src/scripts/seed.ts +212 -0
  23. package/templates/backend/tsconfig.json +24 -0
  24. package/templates/docker/Makefile +86 -0
  25. package/templates/docker/backend/.dockerignore +8 -0
  26. package/templates/docker/backend/Dockerfile +30 -0
  27. package/templates/docker/data/README.md +12 -0
  28. package/templates/docker/data/dump.sql +3 -0
  29. package/templates/docker/docker/.env.example +36 -0
  30. package/templates/docker/docker/frontend.build.defaults +7 -0
  31. package/templates/docker/docker/gitignore +1 -0
  32. package/templates/docker/docker/postgres/01-create-postgres-role.sql +9 -0
  33. package/templates/docker/docker/postgres/docker-entrypoint-wrapper.sh +49 -0
  34. package/templates/docker/docker-compose.infra.yml +64 -0
  35. package/templates/docker/docker-compose.yml +138 -0
  36. package/templates/docker/storefront/.dockerignore +9 -0
  37. package/templates/docker/storefront/Dockerfile +53 -0
  38. package/templates/root/.cursor/commands/docker-dev.md +32 -0
  39. package/templates/root/.cursor/rules/monorepo-layout.mdc +55 -0
  40. package/templates/shared/.cursor/commands/change-theme-colors.md +1 -1
  41. package/templates/shared/.cursor/commands/fix-segment-issue.md +2 -2
  42. package/templates/shared/.cursor/commands/rebuild-storefront.md +1 -1
  43. package/templates/shared/.cursor/rules/customize-theme.mdc +1 -1
  44. package/templates/shared/.cursor/rules/storefront-architecture.mdc +3 -2
  45. package/templates/shared/.cursor/skills/fix-segment-issues/SKILL.md +1 -1
  46. package/templates/shared/README.md +100 -0
  47. package/templates/shared/root-gitignore +6 -0
  48. package/templates/shared/storefront-only-README.md +25 -0
@@ -0,0 +1,212 @@
1
+ import type { ExecArgs } from "@medusajs/framework/types"
2
+ import {
3
+ ContainerRegistrationKeys,
4
+ Modules,
5
+ ProductStatus,
6
+ } from "@medusajs/framework/utils"
7
+ import {
8
+ createApiKeysWorkflow,
9
+ createInventoryLevelsWorkflow,
10
+ createProductCategoriesWorkflow,
11
+ createProductsWorkflow,
12
+ createRegionsWorkflow,
13
+ createSalesChannelsWorkflow,
14
+ createShippingOptionsWorkflow,
15
+ createShippingProfilesWorkflow,
16
+ createStockLocationsWorkflow,
17
+ createTaxRegionsWorkflow,
18
+ linkSalesChannelsToApiKeyWorkflow,
19
+ linkSalesChannelsToStockLocationWorkflow,
20
+ updateStoresWorkflow,
21
+ } from "@medusajs/medusa/core-flows"
22
+
23
+ const SEED_COUNTRIES = ["{{DEFAULT_REGION}}"]
24
+ const SEED_CURRENCY = "{{DEFAULT_CURRENCY}}"
25
+ const SEED_REGION_NAME = "{{DEFAULT_REGION_NAME}}"
26
+
27
+ export default async function seedStore({ container }: ExecArgs) {
28
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
29
+ const link = container.resolve(ContainerRegistrationKeys.LINK)
30
+ const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
31
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
32
+ const storeModuleService = container.resolve(Modules.STORE)
33
+
34
+ logger.info("Seeding store...")
35
+ const [store] = await storeModuleService.listStores()
36
+
37
+ let [salesChannel] = await salesChannelModuleService.listSalesChannels({
38
+ name: "Default Sales Channel",
39
+ })
40
+ if (!salesChannel) {
41
+ const { result } = await createSalesChannelsWorkflow(container).run({
42
+ input: { salesChannelsData: [{ name: "Default Sales Channel" }] },
43
+ })
44
+ salesChannel = result[0]
45
+ }
46
+
47
+ await updateStoresWorkflow(container).run({
48
+ input: {
49
+ selector: { id: store.id },
50
+ update: {
51
+ default_sales_channel_id: salesChannel.id,
52
+ supported_currencies: [{ currency_code: SEED_CURRENCY, is_default: true }],
53
+ },
54
+ },
55
+ })
56
+
57
+ logger.info("Seeding region...")
58
+ const { result: regions } = await createRegionsWorkflow(container).run({
59
+ input: {
60
+ regions: [
61
+ {
62
+ name: SEED_REGION_NAME,
63
+ currency_code: SEED_CURRENCY,
64
+ countries: SEED_COUNTRIES,
65
+ payment_providers: ["pp_system_default"],
66
+ },
67
+ ],
68
+ },
69
+ })
70
+ const region = regions[0]
71
+
72
+ await createTaxRegionsWorkflow(container).run({
73
+ input: SEED_COUNTRIES.map((country_code) => ({
74
+ country_code,
75
+ provider_id: "tp_system",
76
+ })),
77
+ })
78
+
79
+ const { result: stockLocations } = await createStockLocationsWorkflow(container).run({
80
+ input: {
81
+ locations: [
82
+ {
83
+ name: "Primary Warehouse",
84
+ address: { city: "Default", country_code: SEED_COUNTRIES[0], address_1: "" },
85
+ },
86
+ ],
87
+ },
88
+ })
89
+ const stockLocation = stockLocations[0]
90
+
91
+ await updateStoresWorkflow(container).run({
92
+ input: {
93
+ selector: { id: store.id },
94
+ update: { default_location_id: stockLocation.id },
95
+ },
96
+ })
97
+
98
+ await link.create({
99
+ [Modules.STOCK_LOCATION]: { stock_location_id: stockLocation.id },
100
+ [Modules.FULFILLMENT]: { fulfillment_provider_id: "manual_manual" },
101
+ })
102
+
103
+ const shippingProfiles = await fulfillmentModuleService.listShippingProfiles({ type: "default" })
104
+ let shippingProfile = shippingProfiles[0]
105
+ if (!shippingProfile) {
106
+ const { result } = await createShippingProfilesWorkflow(container).run({
107
+ input: { data: [{ name: "Default Shipping Profile", type: "default" }] },
108
+ })
109
+ shippingProfile = result[0]
110
+ }
111
+
112
+ const fulfillmentSet = await fulfillmentModuleService.createFulfillmentSets({
113
+ name: "Primary delivery",
114
+ type: "shipping",
115
+ service_zones: [
116
+ {
117
+ name: SEED_REGION_NAME,
118
+ geo_zones: SEED_COUNTRIES.map((country_code) => ({ country_code, type: "country" as const })),
119
+ },
120
+ ],
121
+ })
122
+
123
+ await link.create({
124
+ [Modules.STOCK_LOCATION]: { stock_location_id: stockLocation.id },
125
+ [Modules.FULFILLMENT]: { fulfillment_set_id: fulfillmentSet.id },
126
+ })
127
+
128
+ await createShippingOptionsWorkflow(container).run({
129
+ input: [
130
+ {
131
+ name: "Standard Shipping",
132
+ price_type: "flat",
133
+ provider_id: "manual_manual",
134
+ service_zone_id: fulfillmentSet.service_zones[0].id,
135
+ shipping_profile_id: shippingProfile.id,
136
+ type: { label: "Standard", description: "Ship in 2-5 days.", code: "standard" },
137
+ prices: [{ currency_code: SEED_CURRENCY, amount: 0 }, { region_id: region.id, amount: 0 }],
138
+ rules: [
139
+ { attribute: "enabled_in_store", value: "true", operator: "eq" },
140
+ { attribute: "is_return", value: "false", operator: "eq" },
141
+ ],
142
+ },
143
+ ],
144
+ })
145
+
146
+ await linkSalesChannelsToStockLocationWorkflow(container).run({
147
+ input: { id: stockLocation.id, add: [salesChannel.id] },
148
+ })
149
+
150
+ logger.info("Creating publishable API key...")
151
+ const { result: apiKeys } = await createApiKeysWorkflow(container).run({
152
+ input: {
153
+ api_keys: [{ title: "Storefront", type: "publishable", created_by: "" }],
154
+ },
155
+ })
156
+ const publishableKey = apiKeys[0]
157
+ await linkSalesChannelsToApiKeyWorkflow(container).run({
158
+ input: { id: publishableKey.id, add: [salesChannel.id] },
159
+ })
160
+
161
+ logger.info("Seeding sample category and product...")
162
+ const { result: categories } = await createProductCategoriesWorkflow(container).run({
163
+ input: { product_categories: [{ name: "Featured", is_active: true }] },
164
+ })
165
+
166
+ const { result: products } = await createProductsWorkflow(container).run({
167
+ input: {
168
+ products: [
169
+ {
170
+ title: "Sample Product",
171
+ handle: "sample-product",
172
+ status: ProductStatus.PUBLISHED,
173
+ category_ids: [categories[0].id],
174
+ options: [{ title: "Size", values: ["S", "M", "L"] }],
175
+ variants: [
176
+ {
177
+ title: "S",
178
+ sku: "SAMPLE-S",
179
+ options: { Size: "S" },
180
+ prices: [{ currency_code: SEED_CURRENCY, amount: 999 }],
181
+ },
182
+ ],
183
+ sales_channels: [{ id: salesChannel.id }],
184
+ },
185
+ ],
186
+ },
187
+ })
188
+
189
+ const inventoryModule = container.resolve(Modules.INVENTORY)
190
+ const inventoryItems = await inventoryModule.listInventoryItems({ sku: "SAMPLE-S" })
191
+ if (inventoryItems[0]) {
192
+ await createInventoryLevelsWorkflow(container).run({
193
+ input: {
194
+ inventory_levels: [
195
+ {
196
+ inventory_item_id: inventoryItems[0].id,
197
+ location_id: stockLocation.id,
198
+ stocked_quantity: 100,
199
+ },
200
+ ],
201
+ },
202
+ })
203
+ }
204
+
205
+ logger.info("")
206
+ logger.info("=== Seed complete ===")
207
+ logger.info(`Region: ${SEED_REGION_NAME} (${SEED_COUNTRIES.join(", ")})`)
208
+ logger.info(`Publishable API key id: ${publishableKey.id}`)
209
+ logger.info("Copy the publishable key token from Medusa Admin → Settings → Publishable API Keys")
210
+ logger.info("into storefront/.env.local as NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY")
211
+ logger.info(`Sample product: ${products[0]?.handle}`)
212
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2021",
4
+ "esModuleInterop": true,
5
+ "module": "Node16",
6
+ "moduleResolution": "Node16",
7
+ "emitDecoratorMetadata": true,
8
+ "experimentalDecorators": true,
9
+ "skipLibCheck": true,
10
+ "declaration": false,
11
+ "outDir": "./.medusa/server",
12
+ "rootDir": "./",
13
+ "jsx": "react-jsx",
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "strictNullChecks": true
17
+ },
18
+ "ts-node": {
19
+ "compiler": "typescript",
20
+ "transpileOnly": true
21
+ },
22
+ "include": ["**/*", ".medusa/types/*"],
23
+ "exclude": ["node_modules", ".medusa/server", ".medusa/admin", ".cache"]
24
+ }
@@ -0,0 +1,86 @@
1
+ .DEFAULT_GOAL := help
2
+
3
+ COMPOSE := docker compose --env-file docker/.env
4
+ COMPOSE_INFRA := docker compose -f docker-compose.infra.yml --env-file docker/.env
5
+ ENV_FILE := docker/.env
6
+ ENV_EXAMPLE := docker/.env.example
7
+
8
+ INFRA_SERVICES := postgres redis mailpit
9
+ BACKEND_SERVICES := postgres redis mailpit backend
10
+ ALL_SERVICES := postgres redis mailpit backend storefront
11
+
12
+ ADMIN_EMAIL ?= {{ADMIN_EMAIL}}
13
+ ADMIN_PASSWORD ?= {{ADMIN_PASSWORD}}
14
+
15
+ .PHONY: help env-init up up-infra up-backend up-storefront down restart build build-backend build-storefront \
16
+ logs logs-backend logs-storefront ps health migrate admin-create seed db-shell mailpit clean
17
+
18
+ help: ## Show available commands
19
+ @grep -E '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
20
+
21
+ env-init: ## Copy docker/.env.example → docker/.env (skip if exists)
22
+ @test -f $(ENV_FILE) || cp $(ENV_EXAMPLE) $(ENV_FILE)
23
+ @echo "Ready: $(ENV_FILE)"
24
+
25
+ up: env-init build ## Build and start full stack in Docker
26
+ $(COMPOSE) up -d $(ALL_SERVICES)
27
+
28
+ up-infra: env-init ## Start postgres, redis, mailpit only (for local npm dev)
29
+ $(COMPOSE_INFRA) up -d $(INFRA_SERVICES)
30
+
31
+ up-backend: env-init build-backend ## Start postgres, redis, mailpit, backend in Docker
32
+ $(COMPOSE) up -d $(BACKEND_SERVICES)
33
+
34
+ migrate: env-init ## Run DB migrations (fresh DB may need two runs)
35
+ -$(COMPOSE) run --rm --no-deps backend npx medusa db:migrate
36
+ $(COMPOSE) run --rm --no-deps backend npx medusa db:migrate
37
+
38
+ up-storefront: env-init build-storefront ## Start storefront container (requires backend)
39
+ $(COMPOSE) up -d storefront
40
+
41
+ down: ## Stop all containers
42
+ -$(COMPOSE) down
43
+ -$(COMPOSE_INFRA) down
44
+
45
+ restart: down up ## Restart full Docker stack
46
+
47
+ build: build-backend build-storefront ## Build all images
48
+
49
+ build-backend: env-init ## Build backend image
50
+ $(COMPOSE) build backend
51
+
52
+ build-storefront: env-init ## Build storefront image
53
+ @test -f $(ENV_FILE) || (echo "Missing $(ENV_FILE). Run: make env-init" && exit 1)
54
+ $(COMPOSE) build storefront
55
+
56
+ logs: ## Tail logs (all services)
57
+ $(COMPOSE) logs -f
58
+
59
+ logs-backend: ## Tail backend logs
60
+ $(COMPOSE) logs -f backend
61
+
62
+ logs-storefront: ## Tail storefront logs
63
+ $(COMPOSE) logs -f storefront
64
+
65
+ ps: ## Show container status
66
+ $(COMPOSE) ps
67
+
68
+ health: ## Check backend and storefront HTTP health
69
+ @curl -sf {{BACKEND_URL}}/health >/dev/null && echo "backend: OK ({{BACKEND_URL}})" || echo "backend: FAIL"
70
+ @curl -sf -o /dev/null -w "" {{BASE_URL}}/{{DEFAULT_REGION}} 2>/dev/null && echo "storefront: OK ({{BASE_URL}})" || echo "storefront: FAIL (start with: make up-storefront or npm run dev:storefront)"
71
+
72
+ admin-create: ## Create admin user (ADMIN_EMAIL / ADMIN_PASSWORD)
73
+ $(COMPOSE) exec backend npx medusa user -e $(ADMIN_EMAIL) -p '$(ADMIN_PASSWORD)'
74
+
75
+ seed: ## Run Medusa seed script (regions, publishable key, sample product)
76
+ $(COMPOSE) exec backend npx medusa exec ./src/scripts/seed.ts
77
+
78
+ db-shell: ## Open psql in postgres container
79
+ $(COMPOSE) exec postgres psql -U {{POSTGRES_USER}} -d {{POSTGRES_DB}}
80
+
81
+ mailpit: ## Open Mailpit UI URL
82
+ @echo "http://localhost:8025"
83
+
84
+ clean: ## Stop containers and remove volumes (destroys DB data)
85
+ $(COMPOSE) down -v
86
+ $(COMPOSE_INFRA) down -v
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ .medusa
3
+ .git
4
+ .env
5
+ .env.*
6
+ npm-debug.log*
7
+ Dockerfile
8
+ .dockerignore
@@ -0,0 +1,30 @@
1
+ FROM node:24-alpine AS builder
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package.json package-lock.json* ./
6
+ RUN npm install --prefer-offline --no-audit --legacy-peer-deps
7
+
8
+ COPY . .
9
+ RUN npm run build
10
+
11
+ RUN if [ -d templates ]; then cp -r templates .medusa/server/; fi
12
+
13
+ FROM node:24-alpine AS runner
14
+
15
+ WORKDIR /app
16
+
17
+ ENV NODE_ENV=production
18
+
19
+ COPY --from=builder /app/.medusa/server ./
20
+ COPY --from=builder /app/medusa-config.ts ./medusa-config.ts
21
+ COPY --from=builder /app/config ./config
22
+ COPY --from=builder /app/package.json ./package.json
23
+ COPY --from=builder /app/package-lock.json* ./
24
+
25
+ RUN npm install --omit=dev --prefer-offline --no-audit --legacy-peer-deps \
26
+ && npm install --save-prod ts-node typescript --prefer-offline --no-audit --legacy-peer-deps
27
+
28
+ EXPOSE 9000
29
+
30
+ CMD ["npm", "run", "start"]
@@ -0,0 +1,12 @@
1
+ # Database dumps
2
+
3
+ Place `dump.sql` here to restore on first postgres startup (see `docker-compose.yml`).
4
+
5
+ On container shutdown, postgres saves a dump here when the database has tables.
6
+
7
+ For fresh installs, use:
8
+
9
+ ```bash
10
+ make migrate
11
+ make seed
12
+ ```
@@ -0,0 +1,3 @@
1
+ -- Optional database restore file (mounted into postgres init).
2
+ -- Fresh installs: leave as-is and run `make migrate` + `make seed` instead.
3
+ SELECT 1;
@@ -0,0 +1,36 @@
1
+ # Copy to docker/.env and fill in secrets only.
2
+ # Usage: docker compose --env-file docker/.env up -d --build
3
+ #
4
+ # Non-critical defaults are in docker-compose.yml.
5
+
6
+ # --- Backend secrets ---
7
+ GOOGLE_CLIENT_ID=
8
+ GOOGLE_CLIENT_SECRET=
9
+ AWS_ACCESS_KEY=
10
+ AWS_SECRET_ACCESS_KEY=
11
+ AWS_BUCKET=
12
+ CASHFREE_CLIENT_ID=
13
+ CASHFREE_CLIENT_SECRET=
14
+ SHIPROCKET_EMAIL=
15
+ SHIPROCKET_PASSWORD=
16
+ SHIPROCKET_WEBHOOK_SECRET=
17
+ TWILIO_ACCOUNT_SID=
18
+ TWILIO_AUTH_TOKEN=
19
+ TWILIO_PHONE=
20
+ FCM_PROJECT_ID=
21
+ FCM_CLIENT_EMAIL=
22
+ FCM_PRIVATE_KEY=
23
+
24
+ # --- Storefront secrets (update publishable key after make seed) ---
25
+ NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY={{PUBLISHABLE_KEY}}
26
+ NEXT_PUBLIC_RAZORPAY_KEY=
27
+ NEXT_PUBLIC_GOOGLE_CLIENT_ID=
28
+ NEXT_PUBLIC_FIREBASE_API_KEY=
29
+ NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
30
+ NEXT_PUBLIC_FIREBASE_PROJECT_ID=
31
+ NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
32
+ NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
33
+ NEXT_PUBLIC_FIREBASE_APP_ID=
34
+ NEXT_PUBLIC_FIREBASE_VAPID_KEY=
35
+ FB_PIXEL_ID=
36
+ FB_ACCESS_TOKEN=
@@ -0,0 +1,7 @@
1
+ MEDUSA_BACKEND_URL=http://backend:9000
2
+ NEXT_PUBLIC_MEDUSA_BACKEND_URL={{BACKEND_URL}}
3
+ NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY={{PUBLISHABLE_KEY}}
4
+ NEXT_PUBLIC_BASE_URL={{BASE_URL}}
5
+ NEXT_PUBLIC_DEFAULT_REGION={{DEFAULT_REGION}}
6
+ NEXT_PUBLIC_CASHFREE_ENVIRONMENT=sandbox
7
+ FILE_LOCAL_BACKEND_URL={{BACKEND_URL}}
@@ -0,0 +1 @@
1
+ .env
@@ -0,0 +1,9 @@
1
+ -- Optional restore helper when using pg_dump in data/dump.sql.
2
+ -- Adds a `postgres` role some dumps expect alongside POSTGRES_USER.
3
+ DO $$
4
+ BEGIN
5
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'postgres') THEN
6
+ CREATE ROLE postgres WITH SUPERUSER LOGIN PASSWORD '{{POSTGRES_PASSWORD}}';
7
+ END IF;
8
+ END
9
+ $$;
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ DUMP_PATH="${DUMP_PATH:-/dump/dump.sql}"
5
+ MIN_TABLES="${MIN_TABLES_FOR_DUMP:-1}"
6
+
7
+ dump_database() {
8
+ if [ -z "${POSTGRES_USER:-}" ] || [ -z "${POSTGRES_DB:-}" ]; then
9
+ return 0
10
+ fi
11
+
12
+ if ! pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then
13
+ return 0
14
+ fi
15
+
16
+ local table_count
17
+ table_count=$(
18
+ psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc \
19
+ "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" \
20
+ 2>/dev/null || echo "0"
21
+ )
22
+
23
+ if [ "${table_count:-0}" -lt "$MIN_TABLES" ]; then
24
+ echo "Skipping dump: public schema has ${table_count:-0} table(s)."
25
+ return 0
26
+ fi
27
+
28
+ echo "Saving database dump (${table_count} tables) to ${DUMP_PATH} ..."
29
+ pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fp > "${DUMP_PATH}.tmp"
30
+ mv "${DUMP_PATH}.tmp" "${DUMP_PATH}"
31
+ echo "Database dump saved."
32
+ }
33
+
34
+ shutdown_postgres() {
35
+ dump_database
36
+
37
+ if [ -n "${child_pid:-}" ]; then
38
+ kill -TERM "$child_pid" 2>/dev/null || true
39
+ wait "$child_pid" 2>/dev/null || true
40
+ fi
41
+
42
+ exit 0
43
+ }
44
+
45
+ trap shutdown_postgres SIGTERM SIGINT
46
+
47
+ /usr/local/bin/docker-entrypoint.sh "$@" &
48
+ child_pid=$!
49
+ wait "$child_pid"
@@ -0,0 +1,64 @@
1
+ # Infrastructure only — use with local Node dev (npm run dev:backend / dev:storefront).
2
+ # Usage: docker compose -f docker-compose.infra.yml --env-file docker/.env up -d
3
+
4
+ x-postgres-user: &postgres-user {{POSTGRES_USER}}
5
+ x-postgres-password: &postgres-password {{POSTGRES_PASSWORD}}
6
+ x-postgres-db: &postgres-db {{POSTGRES_DB}}
7
+
8
+ services:
9
+ postgres:
10
+ image: postgres:18-alpine
11
+ container_name: {{CONTAINER_PREFIX}}-postgres
12
+ restart: unless-stopped
13
+ environment:
14
+ POSTGRES_USER: *postgres-user
15
+ POSTGRES_PASSWORD: *postgres-password
16
+ POSTGRES_DB: *postgres-db
17
+ DUMP_PATH: /dump/dump.sql
18
+ volumes:
19
+ - postgres_data:/var/lib/postgresql
20
+ - ./docker/postgres/01-create-postgres-role.sql:/docker-entrypoint-initdb.d/01-create-postgres-role.sql:ro
21
+ - ./data/dump.sql:/docker-entrypoint-initdb.d/02-dump.sql:ro
22
+ - ./data:/dump
23
+ - ./docker/postgres/docker-entrypoint-wrapper.sh:/usr/local/bin/docker-entrypoint-wrapper.sh:ro
24
+ entrypoint: ["/usr/local/bin/docker-entrypoint-wrapper.sh"]
25
+ command: ["postgres"]
26
+ ports:
27
+ - "5432:5432"
28
+ healthcheck:
29
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
30
+ interval: 5s
31
+ timeout: 5s
32
+ retries: 20
33
+ start_period: 120s
34
+
35
+ redis:
36
+ image: redis:7-alpine
37
+ container_name: {{CONTAINER_PREFIX}}-redis
38
+ restart: unless-stopped
39
+ ports:
40
+ - "6379:6379"
41
+ volumes:
42
+ - redis_data:/data
43
+ healthcheck:
44
+ test: ["CMD", "redis-cli", "ping"]
45
+ interval: 5s
46
+ timeout: 5s
47
+ retries: 10
48
+
49
+ mailpit:
50
+ image: axllent/mailpit
51
+ container_name: {{CONTAINER_PREFIX}}-mailpit
52
+ restart: unless-stopped
53
+ ports:
54
+ - "8025:8025"
55
+ - "1025:1025"
56
+ healthcheck:
57
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025/livez"]
58
+ interval: 5s
59
+ timeout: 5s
60
+ retries: 10
61
+
62
+ volumes:
63
+ postgres_data:
64
+ redis_data:
@@ -0,0 +1,138 @@
1
+ # Usage: docker compose --env-file docker/.env up -d --build
2
+ #
3
+ # Copy docker/.env.example to docker/.env and fill in secrets.
4
+ # Non-critical defaults are in docker-compose.yml (and docker/frontend.build.defaults for image build).
5
+ # Mailpit UI: http://localhost:8025
6
+
7
+ x-postgres-user: &postgres-user {{POSTGRES_USER}}
8
+ x-postgres-password: &postgres-password {{POSTGRES_PASSWORD}}
9
+ x-postgres-db: &postgres-db {{POSTGRES_DB}}
10
+ x-database-url: &database-url {{DATABASE_URL_DOCKER}}
11
+
12
+ x-backend-docker-env: &backend-docker-env
13
+ NODE_ENV: production
14
+ DATABASE_URL: *database-url
15
+ REDIS_URL: redis://redis:6379
16
+ JWT_SECRET: supersecret
17
+ COOKIE_SECRET: supersecret
18
+ STORE_CORS: {{BASE_URL}},{{BACKEND_URL}}
19
+ ADMIN_CORS: {{BACKEND_URL}},http://localhost:7001
20
+ AUTH_CORS: {{BASE_URL}},{{BACKEND_URL}}
21
+ STOREFRONT_URL: {{BASE_URL}}
22
+ MEDUSA_BACKEND_URL: {{BACKEND_URL}}
23
+ GOOGLE_CALLBACK_URL: {{BASE_URL}}/auth/customer/google/callback
24
+ AWS_REGION: ap-south-1
25
+ CASHFREE_ENVIRONMENT: sandbox
26
+ SHIPROCKET_PICKUP_LOCATION: Primary
27
+ SMTP_HOST: mailpit
28
+ SMTP_PORT: "1025"
29
+ SMTP_SECURE: "false"
30
+ SMTP_FROM: noreply@{{CONTAINER_PREFIX}}.local
31
+
32
+ x-storefront-docker-env: &storefront-docker-env
33
+ MEDUSA_BACKEND_URL: http://backend:9000
34
+ NEXT_PUBLIC_MEDUSA_BACKEND_URL: {{BACKEND_URL}}
35
+ NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY: {{PUBLISHABLE_KEY}}
36
+ NEXT_PUBLIC_BASE_URL: {{BASE_URL}}
37
+ NEXT_PUBLIC_DEFAULT_REGION: {{DEFAULT_REGION}}
38
+ NEXT_PUBLIC_CASHFREE_ENVIRONMENT: sandbox
39
+ FILE_LOCAL_BACKEND_URL: {{BACKEND_URL}}
40
+
41
+ secrets:
42
+ frontend_defaults:
43
+ file: ./docker/frontend.build.defaults
44
+ app_env:
45
+ file: ./docker/.env
46
+
47
+ services:
48
+ postgres:
49
+ image: postgres:18-alpine
50
+ container_name: {{CONTAINER_PREFIX}}-postgres
51
+ restart: unless-stopped
52
+ environment:
53
+ POSTGRES_USER: *postgres-user
54
+ POSTGRES_PASSWORD: *postgres-password
55
+ POSTGRES_DB: *postgres-db
56
+ DUMP_PATH: /dump/dump.sql
57
+ volumes:
58
+ - postgres_data:/var/lib/postgresql
59
+ - ./docker/postgres/01-create-postgres-role.sql:/docker-entrypoint-initdb.d/01-create-postgres-role.sql:ro
60
+ - ./data/dump.sql:/docker-entrypoint-initdb.d/02-dump.sql:ro
61
+ - ./data:/dump
62
+ - ./docker/postgres/docker-entrypoint-wrapper.sh:/usr/local/bin/docker-entrypoint-wrapper.sh:ro
63
+ entrypoint: ["/usr/local/bin/docker-entrypoint-wrapper.sh"]
64
+ command: ["postgres"]
65
+ ports:
66
+ - "5432:5432"
67
+ healthcheck:
68
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
69
+ interval: 5s
70
+ timeout: 5s
71
+ retries: 20
72
+ start_period: 120s
73
+
74
+ redis:
75
+ image: redis:7-alpine
76
+ container_name: {{CONTAINER_PREFIX}}-redis
77
+ restart: unless-stopped
78
+ volumes:
79
+ - redis_data:/data
80
+ healthcheck:
81
+ test: ["CMD", "redis-cli", "ping"]
82
+ interval: 5s
83
+ timeout: 5s
84
+ retries: 10
85
+
86
+ mailpit:
87
+ image: axllent/mailpit
88
+ container_name: {{CONTAINER_PREFIX}}-mailpit
89
+ restart: unless-stopped
90
+ ports:
91
+ - "8025:8025"
92
+ healthcheck:
93
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025/livez"]
94
+ interval: 5s
95
+ timeout: 5s
96
+ retries: 10
97
+
98
+ backend:
99
+ build:
100
+ context: ./backend
101
+ dockerfile: Dockerfile
102
+ container_name: {{CONTAINER_PREFIX}}-backend
103
+ env_file:
104
+ - path: docker/.env
105
+ required: false
106
+ environment:
107
+ <<: *backend-docker-env
108
+ ports:
109
+ - "9000:9000"
110
+ depends_on:
111
+ postgres:
112
+ condition: service_healthy
113
+ redis:
114
+ condition: service_healthy
115
+ mailpit:
116
+ condition: service_healthy
117
+
118
+ storefront:
119
+ build:
120
+ context: ./storefront
121
+ dockerfile: Dockerfile
122
+ secrets:
123
+ - frontend_defaults
124
+ - app_env
125
+ container_name: {{CONTAINER_PREFIX}}-storefront
126
+ env_file:
127
+ - path: docker/.env
128
+ required: false
129
+ environment:
130
+ <<: *storefront-docker-env
131
+ ports:
132
+ - "8000:8000"
133
+ depends_on:
134
+ - backend
135
+
136
+ volumes:
137
+ postgres_data:
138
+ redis_data: