@pradip1995/create-storefront-app 0.2.3 → 0.3.1
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.
- package/bin/create-storefront-app.js +147 -59
- package/lib/build-backend-package.js +54 -0
- package/lib/build-homepage-defaults.cjs +50 -0
- package/lib/deps.js +3 -1
- package/lib/docker-template-vars.js +32 -0
- package/lib/medusa-plugin-versions.json +55 -0
- package/lib/scaffold-backend.js +107 -0
- package/lib/scaffold-docker.js +82 -0
- package/lib/versions.js +4 -3
- package/package.json +3 -2
- package/templates/backend/.cursor/commands/seed-backend.md +21 -0
- package/templates/backend/.cursor/rules/medusa-backend.mdc +54 -0
- package/templates/backend/config/README.md +23 -0
- package/templates/backend/config/homepage-config.json +568 -0
- package/templates/backend/env.template +53 -0
- package/templates/backend/gitignore +8 -0
- package/templates/backend/medusa-config.full.ts +251 -0
- package/templates/backend/medusa-config.minimal.ts +141 -0
- package/templates/backend/scripts/build-dynamic-config-defaults.mjs +25 -0
- package/templates/backend/scripts/build-homepage-defaults.cjs +50 -0
- package/templates/backend/src/config/product-metadata-descriptors.ts +27 -0
- package/templates/backend/src/scripts/seed.ts +210 -0
- package/templates/backend/tsconfig.json +24 -0
- package/templates/docker/Makefile +88 -0
- package/templates/docker/backend/.dockerignore +8 -0
- package/templates/docker/backend/Dockerfile +30 -0
- package/templates/docker/data/README.md +12 -0
- package/templates/docker/data/dump.sql +3 -0
- package/templates/docker/docker/.env.example +36 -0
- package/templates/docker/docker/frontend.build.defaults +7 -0
- package/templates/docker/docker/gitignore +1 -0
- package/templates/docker/docker/postgres/01-create-postgres-role.sql +9 -0
- package/templates/docker/docker/postgres/docker-entrypoint-wrapper.sh +49 -0
- package/templates/docker/docker-compose.infra.yml +64 -0
- package/templates/docker/docker-compose.yml +138 -0
- package/templates/docker/storefront/.dockerignore +9 -0
- package/templates/docker/storefront/Dockerfile +53 -0
- package/templates/root/.cursor/commands/docker-dev.md +27 -0
- package/templates/root/.cursor/rules/monorepo-layout.mdc +55 -0
- package/templates/root/scripts/sync-publishable-key.mjs +108 -0
- package/templates/shared/.cursor/commands/change-theme-colors.md +1 -1
- package/templates/shared/.cursor/commands/fix-segment-issue.md +2 -2
- package/templates/shared/.cursor/commands/rebuild-storefront.md +1 -1
- package/templates/shared/.cursor/rules/customize-sections.mdc +7 -1
- package/templates/shared/.cursor/rules/customize-theme.mdc +1 -1
- package/templates/shared/.cursor/rules/storefront-architecture.mdc +3 -2
- package/templates/shared/.cursor/skills/fix-segment-issues/SKILL.md +1 -1
- package/templates/shared/README.md +100 -0
- package/templates/shared/root-gitignore +6 -0
- package/templates/shared/storefront-only-README.md +25 -0
|
@@ -0,0 +1,210 @@
|
|
|
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(`Sample product: ${products[0]?.handle}`)
|
|
210
|
+
}
|
|
@@ -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,88 @@
|
|
|
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, then sync publishable key to storefront env
|
|
76
|
+
$(COMPOSE) exec backend npx medusa exec ./src/scripts/seed.ts
|
|
77
|
+
@token=$$($(COMPOSE) exec -T postgres psql -U {{POSTGRES_USER}} -d {{POSTGRES_DB}} -tAc "SELECT token FROM api_key WHERE type = 'publishable' AND deleted_at IS NULL AND revoked_at IS NULL ORDER BY created_at DESC LIMIT 1" | tr -d '[:space:]'); \
|
|
78
|
+
node scripts/sync-publishable-key.mjs "$$token"
|
|
79
|
+
|
|
80
|
+
db-shell: ## Open psql in postgres container
|
|
81
|
+
$(COMPOSE) exec postgres psql -U {{POSTGRES_USER}} -d {{POSTGRES_DB}}
|
|
82
|
+
|
|
83
|
+
mailpit: ## Open Mailpit UI URL
|
|
84
|
+
@echo "http://localhost:8025"
|
|
85
|
+
|
|
86
|
+
clean: ## Stop containers and remove volumes (destroys DB data)
|
|
87
|
+
$(COMPOSE) down -v
|
|
88
|
+
$(COMPOSE_INFRA) down -v
|
|
@@ -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,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:
|