Makefile
SHELL := /usr/bin/env bash
# define standard colors
ifneq (,$(findstring xterm,${TERM}))
BLACK := $(shell tput -Txterm setaf 0)
RED := $(shell tput -Txterm setaf 1)
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
LIGHTPURPLE := $(shell tput -Txterm setaf 4)
PURPLE := $(shell tput -Txterm setaf 5)
BLUE := $(shell tput -Txterm setaf 6)
WHITE := $(shell tput -Txterm setaf 7)
RESET := $(shell tput -Txterm sgr0)
else
BLACK := ""
RED := ""
GREEN := ""
YELLOW := ""
LIGHTPURPLE := ""
PURPLE := ""
BLUE := ""
WHITE := ""
RESET := ""
endif
ENV_PATH ?= ./apps/nextjs-app
DOCKER_COMPOSE ?= docker compose
DOCKER_COMPOSE_ENV_FILE := $(wildcard ./dockers/.env)
COMPOSE_FILES := $(wildcard ./dockers/*.yml)
COMPOSE_FILE_ARGS := --env-file $(DOCKER_COMPOSE_ENV_FILE) $(foreach yml,$(COMPOSE_FILES),-f $(yml))
NETWORK_MODE ?= teablenet
CI_JOB_ID ?= 0
CI ?= 0
# Timeout used to await services to become healthy
TIMEOUT ?= 300
SCRATCH ?= /tmp
UNAME_S := $(shell uname -s)
# prisma database url defaults
SQLITE_PRISMA_DATABASE_URL ?= file:../../db/main.db
# set param statement_cache_size=1 to avoid query error `ERROR: cached plan must not change result type` after alter column type (modify field type)
POSTGES_PRISMA_DATABASE_URL ?= postgresql://teable:teable\@127.0.0.1:5432/teable?schema=public\&statement_cache_size=1
# If the first make argument is "start", "stop"...
ifeq (docker.start,$(firstword $(MAKECMDGOALS)))
SERVICE_TARGET = true
else ifeq (docker.stop,$(firstword $(MAKECMDGOALS)))
SERVICE_TARGET = true
else ifeq (docker.restart,$(firstword $(MAKECMDGOALS)))
SERVICE_TARGET = true
else ifeq (docker.up,$(firstword $(MAKECMDGOALS)))
SERVICE_TARGET = true
else ifeq (docker.await,$(firstword $(MAKECMDGOALS)))
SERVICE_TARGET = true
else ifeq (docker.run,$(firstword $(MAKECMDGOALS)))
RUN_TARGET = true
else ifeq (docker.integration,$(firstword $(MAKECMDGOALS)))
INTEGRATION_TARGET = true
endif
ifdef SERVICE_TARGET
# .. then use the rest as arguments for the make target
SERVICE := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# ...and turn them into do-nothing targets
$(eval $(SERVICE):;@:)
else ifdef RUN_TARGET
# Isolate second argument as service, the rest is arguments for run command
SERVICE := $(wordlist 2, 2, $(MAKECMDGOALS))
SERVICE_ARGS := $(wordlist 3, $(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
else ifdef INTEGRATION_TARGET
# Isolate second argument as integration module, the rest as arguments
INTEGRATION_MODULE := $(wordlist 2, 2, $(MAKECMDGOALS))
$(eval $(INTEGRATION_MODULE):;@:)
INTEGRATION_ARGS := $(wordlist 3, $(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
$(eval $(INTEGRATION_ARGS):;@:)
endif
#
# Never use the network=host mode when running CI jobs, and add extra
# distinguishing identifiers to the network name and container names to
# prevent collisions with jobs from the same project running at the same
# time.
#
ifneq ($(CI_JOB_ID),)
NETWORK_MODE := teablenet-$(CI_JOB_ID)
endif
ifeq ($(CI),0)
export NODE_ENV = development
endif
ifeq ($(UNAME_S),Linux)
DOCKER_GID ?= $(shell getent group docker | cut -d: -f 3)
else ifeq ($(UNAME_S),Darwin)
DOCKER_GID ?= $(shell id -g)
else
$(error Sorry, '${UNAME_S}' is not supported yet)
endif
DOCKER_COMPOSE_ARGS := DOCKER_UID=$(shell id -u) \
DOCKER_GID=$(DOCKER_GID) \
NETWORK_MODE=$(NETWORK_MODE)
define print_db_mode_options
@echo -e "\nSelect a database to start.\n"
@echo -e "1)sqlite Lightweight embedded, ideal for mobile and embedded systems, simple, resource-efficient, easy integration (default database)"
@echo -e "2)postges(pg) Powerful and scalable, suitable for complex enterprise needs, highly customizable, rich community support\n"
endef
define print_db_push_options
@echo -e "The 'db pull' command connects to your database and adds Prisma models to your Prisma schema that reflect the current database schema.\n"
@echo -e "1) sqlite"
@echo -e "2) postges(pg)\n"
endef
.PHONY: db-mode sqlite.mode postgres.mode gen-prisma-schema gen-sqlite-prisma-schema gen-postgres-prisma-schema
.DEFAULT_GOAL := help
docker.create.network:
ifneq ($(NETWORK_MODE),host)
@docker network inspect $(NETWORK_MODE) &> /dev/null || ([ $$? -ne 0 ] && docker network create $(NETWORK_MODE))
$(info ${GREEN}network $(NETWORK_MODE) create success${RESET})
endif
docker.rm.network:
ifneq ($(NETWORK_MODE),host)
@docker network inspect $(NETWORK_MODE) &> /dev/null && ([ $$? -eq 0 ] && docker network rm $(NETWORK_MODE)) || true
$(warning ${GREEN}network $(NETWORK_MODE) removed${RESET})
endif
docker.run: docker.create.network
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) run -T --no-deps --rm $(SERVICE) $(SERVICE_ARGS)
docker.up: docker.create.network
@$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) up --no-recreate -d $(SERVICE)
docker.down: docker.rm.network
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) down
docker.start:
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) start $(SERVICE)
docker.stop:
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) stop $(SERVICE)
docker.restart:
make docker.stop $(SERVICE)
make docker.start $(SERVICE)
TIME := 0
docker.await: ## max timeout of 300
@time=$(TIME); \
for i in $(SERVICE); do \
current_service=$$($(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) ps -q $${i}); \
if [ -z "$${current_service}" ]; then \
continue; \
fi; \
service_has_health=$$(docker inspect -f '{{.State.Health.Status}}' $${current_service}); \
if [ -z "$${service_has_health}" ]; then \
continue; \
fi; \
while [ "$$(docker inspect -f '{{.State.Health.Status}}' $${current_service})" != "healthy" ] ; do \
sleep 1; \
time=$$(expr $$time + 1); \
if [ $${time} -gt $(TIMEOUT) ]; then \
echo "${YELLOW}Timeout reached waiting for $${i} to become healthy${RESET}"; \
docker logs $${i}; \
exit 1; \
fi; \
done; \
echo "${GREEN}Service $${i} is healthy${RESET}"; \
done
docker.status:
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) ps
docker.images:
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) images
build.app:
@zx --version || pnpm add -g zx; \
zx scripts/build-image.mjs --file=dockers/teable/Dockerfile \
--tag=teable:develop
build.db-migrate:
@zx --version || pnpm add -g zx; \
zx scripts/build-image.mjs --file=dockers/teable/Dockerfile.db-migrate \
--tag=teable-db-migrate:develop
sqlite.integration.test:
@export PRISMA_DATABASE_URL='file:../../db/main.db'; \
export CALC_CHUNK_SIZE=400; \
make sqlite.mode; \
pnpm -F "./packages/**" run build; \
pnpm g:test-e2e-cover
postgres.integration.test: docker.create.network
@TEST_PG_CONTAINER_NAME=teable-postgres-$(CI_JOB_ID); \
docker rm -fv $$TEST_PG_CONTAINER_NAME | true; \
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) run -p 25432:5432 -d -T --no-deps --rm --name $$TEST_PG_CONTAINER_NAME teable-postgres; \
chmod +x scripts/wait-for; \
scripts/wait-for 127.0.0.1:25432 --timeout=15 -- echo 'pg database started successfully' && \
export PRISMA_DATABASE_URL=postgresql://teable:teable@127.0.0.1:25432/e2e_test_teable?schema=public\&statement_cache_size=1 && \
make postgres.mode && \
pnpm -F "./packages/**" run build && \
pnpm g:test-e2e-cover && \
docker rm -fv $$TEST_PG_CONTAINER_NAME
gen-sqlite-prisma-schema:
@cd ./packages/db-main-prisma; \
echo '{ "PRISMA_PROVIDER": "sqlite" }' | pnpm mustache - ./prisma/template.prisma > ./prisma/sqlite/schema.prisma
@echo 'generate【 prisma/sqlite/schema.prisma 】success.'
gen-postgres-prisma-schema:
@cd ./packages/db-main-prisma; \
echo '{ "PRISMA_PROVIDER": "postgres" }' | pnpm mustache - ./prisma/template.prisma > ./prisma/postgres/schema.prisma
@echo 'generate【 prisma/postgres/schema.prisma 】success.'
gen-prisma-schema: gen-sqlite-prisma-schema gen-postgres-prisma-schema ## Generate 'schema.prisma' files for all versions of the system
sqlite-db.push: ## db.push by sqlite
@cd ./packages/db-main-prisma; \
pnpm prisma-db-push --schema ./prisma/sqlite/schema.prisma
postgres-db.push: ## db.push by postgres
@cd ./packages/db-main-prisma; \
pnpm prisma-db-push --schema ./prisma/postgres/schema.prisma
db.push: ## connects to your database and adds Prisma models to your Prisma schema that reflect the current database schema.
$(print_db_push_options)
@read -p "Enter a command: " command; \
if [ "$$command" = "1" ] || [ "$$command" = "sqlite" ]; then \
make gen-sqlite-prisma-schema; \
make sqlite-db.push; \
elif [ "$$command" = "2" ] || [ "$$command" = "postges" ] || [ "$$command" = "pg" ]; then \
make gen-postgres-prisma-schema; \
make postgres-db.push; \
else echo "Unknown command."; fi
sqlite-db-migration:
@_MIGRATION_NAME=$(if $(_MIGRATION_NAME),$(_MIGRATION_NAME),`read -p "Enter name of the migration (sqlite): " migration_name; echo $$migration_name`); \
make gen-sqlite-prisma-schema; \
PRISMA_DATABASE_URL=file:../../db/.shadow/main.db \
pnpm -F @teable/db-main-prisma prisma-migrate dev --schema ./prisma/sqlite/schema.prisma --name $$_MIGRATION_NAME
postgres-db-migration:
@_MIGRATION_NAME=$(if $(_MIGRATION_NAME),$(_MIGRATION_NAME),`read -p "Enter name of the migration (postgres): " migration_name; echo $$migration_name`); \
make gen-postgres-prisma-schema; \
PRISMA_DATABASE_URL=postgresql://teable:teable@127.0.0.1:5432/teable?schema=shadow \
pnpm -F @teable/db-main-prisma prisma-migrate dev --schema ./prisma/postgres/schema.prisma --name $$_MIGRATION_NAME
db-migration: ## Reruns the existing migration history in the shadow database in order to detect schema drift (edited or deleted migration file, or a manual changes to the database schema)
@read -p "Enter name of the migration: " migration_name; \
make sqlite-db-migration _MIGRATION_NAME=$$migration_name; \
make postgres-db-migration _MIGRATION_NAME=$$migration_name
sqlite.mode: ## sqlite.mode
@cd ./packages/db-main-prisma; \
pnpm prisma-generate --schema ./prisma/sqlite/schema.prisma; \
pnpm prisma-migrate deploy --schema ./prisma/sqlite/schema.prisma
postgres.mode: ## postgres.mode
@cd ./packages/db-main-prisma; \
pnpm prisma-generate --schema ./prisma/postgres/schema.prisma; \
pnpm prisma-migrate deploy --schema ./prisma/postgres/schema.prisma
# Override environment variable files based on variables
RUN_DB_MODE ?= sqlite
FILE_ENV_PATHS = $(ENV_PATH)/.env.development* $(ENV_PATH)/.env.test*
switch.prisma.env:
ifeq ($(CI)-$(RUN_DB_MODE),0-sqlite)
@for file in $(FILE_ENV_PATHS); do \
echo $$file; \
perl -i -pe 's~^PRISMA_DATABASE_URL=.*~PRISMA_DATABASE_URL=$(SQLITE_PRISMA_DATABASE_URL)~' $$file; \
if ! grep -q '^CALC_CHUNK_SIZE=' $$file; then \
echo "CALC_CHUNK_SIZE=400" >> $$file; \
else \
perl -i -pe 's~^CALC_CHUNK_SIZE=.*~CALC_CHUNK_SIZE=400~' $$file; \
fi; \
done
else ifeq ($(CI)-$(RUN_DB_MODE),0-postges)
@for file in $(FILE_ENV_PATHS); do \
echo $$file; \
perl -i -pe 's~^PRISMA_DATABASE_URL=.*~PRISMA_DATABASE_URL=$(POSTGES_PRISMA_DATABASE_URL)~' $$file; \
done
endif
switch-db-mode: ## Switch Database environment
$(print_db_mode_options)
@read -p "Enter a command: " command; \
if [ "$$command" = "1" ] || [ "$$command" = "sqlite" ]; then \
make switch.prisma.env RUN_DB_MODE=sqlite; \
make sqlite.mode; \
elif [ "$$command" = "2" ] || [ "$$command" = "postges" ] || [ "$$command" = "pg" ]; then \
make switch.prisma.env RUN_DB_MODE=postges; \
make docker.up teable-postgres; \
make docker.await teable-postgres; \
make postgres.mode; \
else \
echo "Unknown command."; fi
help: ## show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'