Автоматизированный и версионированный импорт типов из GraphQL-схемы
Недавно работая над GraphQL-проектом, у меня была задача: схему (часть Infrastructure-as-Code) нужно было экспортировать в TypeScript для фронтенда и бэкенда. Я быстро понял, что поскольку схема эволюционирует по мере добавления фич, я трачу время на а) ручные действия и б) сравнение версий типов между схемой, фронтом и бэком — что могло приводить к ошибкам. Поэтому придумал быстрое и простое решение.
Для начала генерируем типы из схемы. Используем graphql-codegen — устанавливаем в репозитории со схемой GraphQL:
npm -i @graphql-codegen/typescript @graphql-codegen/cli --save-dev
И настраиваем через codegen.ts в корне того же репозитория:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "path/to/your/schema.graphql",
generates: {
"graphql-types.ts": {
plugins: ["typescript"],
},
},
};
export default config;
Проверяем, что работает: graphql-codegen --config codegen.ts. Затем создаем bash-скрипт generate-types.sh, который создает локальный npm-пакет:
#!/bin/bash
# Usage: ./generate-types.sh <version>
# Example: ./generate-types.sh 1.2.3
# Check if a version argument is provided
if [ -z "$1" ]; then
echo "Error: Version not specified."
echo "Usage: ./generate-types.sh <version>"
exit 1
fi
VERSION=$1
# Configuration variables
PACKAGE_NAME="@my-project/graphql-types"
FILENAME="graphql-types.ts"
# Generate graphql-types.ts
echo "Generating $FILENAME..."
graphql-codegen --config codegen.ts
# Prepare a temporary directory for publishing
echo "Preparing package $PACKAGE_NAME for version $VERSION..."
mkdir -p ./dist
mv ./$FILENAME ./dist/index.d.ts
# Create a minimal package.json in the dist folder with the specified version
echo "{
\"name\": \"$PACKAGE_NAME\",
\"version\": \"$VERSION\",
\"types\": \"index.d.ts\"
}" > ./dist/package.json
# Create the package
echo "Packaging $PACKAGE_NAME version $VERSION..."
npm pack ./dist
# Clean up
echo "Cleaning up..."
rm -rf ./dist
echo "$PACKAGE_NAME@$VERSION generated successfully!"
Этот скрипт я добавил в инфраструктурный репозиторий. Можно было бы завести отдельный package.json — и это правильно — но я предпочитаю держать все связанное в одном месте. Вызов скрипта с версией создаст пакет этой версии. Я предпочитаю коммитить этот пакет с номером версии.
Теперь импортируем его во фронтенд или бэкенд. Для этого я тоже написал скрипт:
#!/bin/bash
# Usage: ./import-types.sh <version>
# Example: ./import-types.sh 1.2.3
# Define the paths to the repositories
EXPORTER_DIR="./../my-project"
IMPORTER_DIR="."
# Check if a version argument is provided
if [ -z "$1" ]; then
echo "Error: Version not specified."
echo "Usage: ./import-package.sh <version>"
exit 1
fi
# Set the package name with the provided version
PACKAGE_NAME="my-project-graphql-types"
PACKAGE_VERSION="$1"
PACKAGE_FILE="$PACKAGE_NAME-$PACKAGE_VERSION.tgz"
# Check if the exporter directory exists
if [ ! -d "$EXPORTER_DIR" ]; then
echo "Error: Exporter folder $EXPORTER_DIR not found." exit 1
fi
# Check if the package file exists
if [ ! -f "$EXPORTER_DIR/$PACKAGE_FILE" ]; then
echo "Error: Package $PACKAGE_FILE not found in $EXPORTER_DIR."
exit 1
fi
# Navigate to the importer directory
echo "Navigating to $IMPORTER_DIR..."
cd "$IMPORTER_DIR" || { echo "Error: Could not navigate to $IMPORTER_DIR."; exit 1; }
echo "Cleaning existing versions of $PACKAGE_NAME..."
rm -f "${PACKAGE_NAME}-"*.tgz
# Copy package to importer directory
echo "Copying $PACKAGE_FILE to $IMPORTER_DIR..."
cp "$EXPORTER_DIR/$PACKAGE_FILE" . || { echo "Error: Could not copy $PACKAGE_FILE to $IMPORTER_DIR."; exit 1; }
# Install the package from the exporter repository
echo "Installing package $PACKAGE_FILE..."
npm install "$PACKAGE_FILE"
# Check if the installation was successful
if [ $? -eq 0 ]; then
echo "Package $PACKAGE_FILE installed successfully in $IMPORTER_DIR!"
else
echo "Error: Failed to install package $PACKAGE_FILE."
exit 1
fi
Этот скрипт коммитим в репозитории фронтенда и бэкенда и запускаем так же — с версией в аргументе. Теперь IDE подхватит типы:
import { MyType } from "@my-project/graphql-types";
function somewhereInFrontend(obj: MyType) {}
Очевидно, процесс можно улучшить — выбирать последнюю версию в репозитории со схемой и добавить нормальную публикацию через npm (тогда второй скрипт не понадобится). Но мой рабочий процесс уже улучшился:
- одна команда в инфраструктурном репозитории
- одна команда в репозитории фронтенда и бэкенда
- автоматическая генерация типов
- версионирование
Надеюсь, поможет и улучшит ваш процесс тоже.