mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 05:09:39 +02:00
Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
176 lines
6 KiB
Markdown
176 lines
6 KiB
Markdown
# SSH URLs in package-lock.json - The Complete Solution
|
|
|
|
## TL;DR
|
|
|
|
**Two-layer approach:**
|
|
1. **CI test stage**: Patch lockfile SSH → HTTPS for `npm ci` (for tests)
|
|
2. **Docker build**: Clone private repo, build tarball, replace with `file:` (for production image)
|
|
|
|
## The Problem
|
|
|
|
Your local machine converts HTTPS → SSH during `npm install`, baking SSH URLs into `package-lock.json`. CI/CD fails because it can't authenticate via SSH.
|
|
|
|
## Why Fighting It Locally Doesn't Work
|
|
|
|
❌ **Approach 1**: "Fix package.json to use HTTPS"
|
|
- Doesn't work if git config rewrites it during install
|
|
|
|
❌ **Approach 2**: "Remove SSH rewrites from git config"
|
|
- Inconvenient for developers
|
|
- Easy to forget
|
|
- Breaks other workflows
|
|
|
|
❌ **Approach 3**: "Temporarily disable git config during install"
|
|
- Doesn't persist
|
|
- Every developer needs to remember
|
|
|
|
## ✅ The Complete Solution (Two-Layer Approach)
|
|
|
|
**Accept that the lockfile has SSH URLs and handle them at two stages:**
|
|
|
|
### Layer 1: CI Test Stage (For Running Tests)
|
|
|
|
The GitHub Actions workflow uses the proven pattern to handle both SSH and HTTPS URLs:
|
|
|
|
```yaml
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false # Don't let default GITHUB_TOKEN interfere
|
|
|
|
- name: Configure git for private packages
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
|
run: |
|
|
git config --global url."https://${GH_TOKEN}@github.com/".insteadOf ssh://git@github.com/
|
|
git config --global url."https://${GH_TOKEN}@github.com/".insteadOf git@github.com:
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '18'
|
|
cache: 'npm'
|
|
|
|
- name: Patch package-lock.json with authenticated URLs
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
|
run: |
|
|
# Handle both SSH and HTTPS URLs
|
|
if grep -q "git+ssh://git@github.com" package-lock.json; then
|
|
echo "⚠️ SSH URLs found - patching to HTTPS with token..."
|
|
sed -i "s|git+ssh://git@github.com/Memo-2023/|git+https://${GH_TOKEN}@github.com/Memo-2023/|g" package-lock.json
|
|
echo "✓ Lockfile patched successfully"
|
|
else
|
|
echo "⚠️ HTTPS URLs found - injecting token..."
|
|
sed -i "s|git+https://github.com/Memo-2023/|git+https://${GH_TOKEN}@github.com/Memo-2023/|g" package-lock.json
|
|
echo "✓ Token injected successfully"
|
|
fi
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --legacy-peer-deps
|
|
```
|
|
|
|
### Layer 2: Docker Build (For Production Image)
|
|
|
|
The Dockerfile clones the private repo, builds it as a tarball, and installs from `file:`:
|
|
|
|
```dockerfile
|
|
# Clone, build and package mana-core as a tarball
|
|
RUN --mount=type=secret,id=github_token \
|
|
if [ -f /run/secrets/github_token ]; then \
|
|
export GITHUB_TOKEN=$(cat /run/secrets/github_token) && \
|
|
git clone https://${GITHUB_TOKEN}@github.com/Memo-2023/mana-core-nestjs-package.git /tmp/mana-core; \
|
|
fi && \
|
|
cd /tmp/mana-core && \
|
|
npm install --force && \
|
|
npm run build && \
|
|
npm pack && \
|
|
mv *.tgz /app/mana-core.tgz
|
|
|
|
# Copy package.json and replace GitHub URL with the tarball
|
|
COPY package.json ./
|
|
RUN sed -i 's|"git+https://github.com/Memo-2023/mana-core-nestjs-package.git"|"file:mana-core.tgz"|g' package.json
|
|
|
|
# Install dependencies from tarball
|
|
RUN npm install --legacy-peer-deps
|
|
```
|
|
|
|
The GitHub Actions workflow passes the token as a Docker secret:
|
|
|
|
```yaml
|
|
- name: Build and Push Docker Image
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
|
run: |
|
|
docker build \
|
|
--secret id=github_token,env=GH_TOKEN \
|
|
.
|
|
```
|
|
|
|
## Why This Works
|
|
|
|
1. **Developers**: Use SSH locally (convenient, no config changes needed)
|
|
2. **package-lock.json**: Contains SSH URLs (fine, we handle it in CI)
|
|
3. **CI Test Stage**: Patches SSH → HTTPS for `npm ci` to run tests
|
|
4. **Docker Build**: Clones repo, builds tarball, installs from `file:` (no git involved in final image)
|
|
5. **Production**: Docker image has mana-core built into it, no runtime git dependency
|
|
6. **Everyone's happy**: No git config changes, no local workflow disruption
|
|
|
|
## Key Implementation Details
|
|
|
|
### For CI Test Stage (sed patching)
|
|
|
|
```bash
|
|
sed -i "s|git+ssh://git@github.com/Memo-2023/|git+https://${GH_TOKEN}@github.com/Memo-2023/|g" package-lock.json
|
|
```
|
|
|
|
- Happens at runtime in CI (never committed)
|
|
- Allows `npm ci` to install dependencies for testing
|
|
- Works reliably every time
|
|
|
|
### For Docker Build (tarball approach)
|
|
|
|
```bash
|
|
# Docker secret gives access to GitHub token
|
|
--mount=type=secret,id=github_token
|
|
|
|
# Clone and build the private package
|
|
git clone https://${GITHUB_TOKEN}@github.com/Memo-2023/mana-core-nestjs-package.git
|
|
npm pack # Creates .tgz file
|
|
|
|
# Replace git URL with local file reference
|
|
sed -i 's|"git+https://..."|"file:mana-core.tgz"|g' package.json
|
|
```
|
|
|
|
- Private package is baked into the Docker image
|
|
- No git dependency at runtime
|
|
- Production image is fully self-contained
|
|
|
|
## Why This is Better Than Alternatives
|
|
|
|
| Approach | Developer Impact | Reliability | Production Quality | Maintenance |
|
|
|----------|-----------------|-------------|-------------------|-------------|
|
|
| Fix git config locally | 😡 High | 🔴 Low | ⚠️ Medium | 😱 High |
|
|
| Require HTTPS in lockfile | 😡 High | 🔴 Low | ⚠️ Medium | 😱 High |
|
|
| **Two-layer (sed + tarball)** | 😊 None | 🟢 100% | ✅ Excellent | 😌 None |
|
|
|
|
## Lessons Learned
|
|
|
|
1. **npm ci reads package-lock.json directly** - It doesn't care about git config
|
|
2. **Fighting developer workflows is futile** - Accept SSH URLs locally
|
|
3. **Two layers solve different problems**:
|
|
- CI test stage needs quick install for testing → sed patch
|
|
- Production image needs reliability and security → tarball bake-in
|
|
4. **Docker secrets are the right tool** - Pass credentials without committing them
|
|
5. **Self-contained images are better** - No runtime git dependencies
|
|
|
|
## References
|
|
|
|
This solution combines proven approaches:
|
|
- **sed patching**: Used in `storyteller-project` for CI/CD
|
|
- **tarball approach**: Used in memoro-service for production Docker images
|
|
- Battle-tested across multiple projects
|
|
|
|
---
|
|
|
|
**Bottom line**: Commit the SSH lockfile, handle it in two layers (CI + Docker). Done. 🎯
|