stages:
- validate
- generate
- build
- test
- package
variables:
NODE_VERSION: "20"
CACHE_KEY: "${CI_COMMIT_REF_SLUG}-yarn-cache"
YARN_CACHE_FOLDER: ".yarn/cache"
# Default template for all Node.js jobs
.node_template: &node_template
image: node:${NODE_VERSION}-alpine
cache:
key: $CACHE_KEY
paths:
- .yarn/cache/
- node_modules/
policy: pull-push
before_script:
- apk add --no-cache git
- corepack enable
- corepack prepare yarn@4.9.2 --activate
- yarn install --immutable --check-cache
# Validation stage
type_check:
<<: *node_template
stage: validate
script:
- yarn type-check
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure: false
security_scan:
<<: *node_template
stage: validate
script:
- yarn npm audit --all --recursive --environment production
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Generation stage - Generate tools
generate_tools:
<<: *node_template
stage: generate
script:
- echo "Generating modular tool definitions..."
- yarn generate-tools
- echo "Verifying generated tools..."
- test -f tools-generated.json || (echo "Missing tools-generated.json" && exit 1)
- echo "Tool generation completed successfully"
artifacts:
paths:
- tools-generated.json
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Build stage
build:
<<: *node_template
stage: build
dependencies:
- generate_tools
script:
- echo "Building MCP server..."
- yarn build
- ls -la .
- echo "Verifying build outputs..."
- test -f index.js || (echo "Missing index.js" && exit 1)
- test -s index.js || (echo "index.js is empty" && exit 1)
- echo "Build completed successfully"
artifacts:
paths:
- index.js
- tools-generated.json
expire_in: 1 week
after_script:
- |
echo "Build artifacts:"
ls -lh index.js tools-generated.json
echo "Total size: $(du -sh index.js tools-generated.json | tail -n1 | cut -f1)"
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Test stage
test_build_artifacts:
<<: *node_template
stage: test
dependencies:
- build
script:
- |
echo "Validating build artifacts..."
# Check required files exist
test -f index.js || (echo "Missing index.js" && exit 1)
test -f tools-generated.json || (echo "Missing tools-generated.json" && exit 1)
# Check files are not empty
test -s index.js || (echo "index.js is empty" && exit 1)
test -s tools-generated.json || (echo "tools-generated.json is empty" && exit 1)
# Try to load the built module
node -e "import('./index.js').then(() => console.log('ESM import successful'));"
# Test MCP server functionality
echo "Testing MCP server help..."
node index.js --help > /dev/null 2>&1 && echo "Help validation passed" || echo "Help validation completed"
echo "Validation step completed successfully"
echo "All build artifacts validated successfully"
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
validate_tools:
<<: *node_template
stage: test
dependencies:
- build
script:
- |
echo "Validating generated tools..."
# Check tools-generated.json is valid JSON and has expected structure
node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('tools-generated.json', 'utf8'));
// Validate structure
if (!data.tools || !Array.isArray(data.tools)) {
throw new Error('tools-generated.json must contain a tools array');
}
if (data.tools.length === 0) {
throw new Error('No tools found in tools-generated.json');
}
// Validate tool structure
data.tools.forEach((tool, index) => {
if (!tool.name) throw new Error('Tool ' + index + ' missing name');
if (!tool.description) throw new Error('Tool ' + index + ' missing description');
if (!tool.module) throw new Error('Tool ' + index + ' missing module');
if (!tool.methods || !Array.isArray(tool.methods)) {
throw new Error('Tool ' + index + ' missing methods array');
}
if (!tool.inputSchema) throw new Error('Tool ' + index + ' missing inputSchema');
});
console.log('Total tools: ' + data.totalTools);
console.log('Core tools: ' + data.coreTools);
console.log('Plugin tools: ' + data.pluginTools);
console.log('Tools validation passed');
"
echo "Tool validation completed successfully"
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Package validation stage
validate_package:
<<: *node_template
stage: package
dependencies:
- build
script:
- |
echo "Validating package structure..."
# Validate package.json exports
node -e "
const pkg = require('./package.json');
const fs = require('fs');
// Check main entry points exist
if (!fs.existsSync(pkg.main)) {
throw new Error('Main entry point does not exist: ' + pkg.main);
}
if (!fs.existsSync(pkg.module)) {
throw new Error('Module entry point does not exist: ' + pkg.module);
}
// Check bin entry
if (pkg.bin && !fs.existsSync(pkg.bin)) {
throw new Error('Bin entry point does not exist: ' + pkg.bin);
}
// Check files list
const expectedFiles = ['index.js', 'tools-generated.json', 'README.md', 'LICENSE'];
expectedFiles.forEach(file => {
if (!fs.existsSync(file)) {
throw new Error('Expected file does not exist: ' + file);
}
});
console.log('Package structure validation passed');
"
# Test package installation simulation
yarn pack --dry-run
echo "Package validation completed successfully"
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
package_tarball:
<<: *node_template
stage: package
dependencies:
- build
script:
- yarn pack
- ls -la *.tgz
artifacts:
paths:
- "*.tgz"
expire_in: 1 week
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
# Performance monitoring
bundle_analysis:
<<: *node_template
stage: test
dependencies:
- build
script:
- |
echo "Analyzing bundle size..."
# Calculate sizes
MAIN_SIZE=$(stat -f%z index.js 2>/dev/null || stat -c%s index.js)
TOOLS_SIZE=$(stat -f%z tools-generated.json 2>/dev/null || stat -c%s tools-generated.json)
TOTAL_SIZE=$((MAIN_SIZE + TOOLS_SIZE))
echo "Bundle sizes:"
echo " index.js: ${MAIN_SIZE} bytes"
echo " tools-generated.json: ${TOOLS_SIZE} bytes"
echo " Total: ${TOTAL_SIZE} bytes"
# Warn if bundle is too large (>2MB total)
if [ $TOTAL_SIZE -gt 2097152 ]; then
echo "⚠️ Warning: Total bundle size exceeds 2MB"
fi
# Create size report
cat > bundle-size-report.json << EOF
{
"main_size": $MAIN_SIZE,
"tools_size": $TOOLS_SIZE,
"total_size": $TOTAL_SIZE,
"commit": "$CI_COMMIT_SHA",
"ref": "$CI_COMMIT_REF_NAME"
}
EOF
echo "Bundle analysis completed"
artifacts:
paths:
- bundle-size-report.json
expire_in: 30 days
reports:
performance: bundle-size-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure: true