Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ jobs:
wait-on-timeout: 120
command: npm run cypress:run

- name: Smoke test build
run: bash ./scripts/package-smoke-test.sh --no-clean-build

# Windows build - single combination for development support
build-windows:
runs-on: windows-latest
Expand Down Expand Up @@ -135,6 +138,7 @@ jobs:
npm run test-coverage-ci
npm run test-coverage-ci --workspaces --if-present

# Smoke test deliberately removed due to long running times
- name: Build frontend
run: npm run build-ui

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
- run: npm ci
- run: npm run build

- name: Smoke test package before publishing
run: ./scripts/package-smoke-test.sh --no-clean-build

- name: Determine dist-tag and publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,10 @@ git-proxy/
### Building

```bash
npm run build # Full build: generate config types, build UI, compile TypeScript
npm run build-ts # Compile TypeScript server code to dist/
npm run build-ui # Build React frontend with Vite to build/
npm run build # Full build: generate config types, build UI, compile TypeScript
npm run build-ts # Compile TypeScript server code to dist/
npm run build-ui # Build React frontend with Vite to build/
npm run build-validate # Check that UI files are correctly included in build
```

### Type checking
Expand Down
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends git tini \
COPY --chown=1000:1000 --from=builder /out/package*.json ./
COPY --chown=1000:1000 --from=builder /out/node_modules/ ./node_modules/
COPY --chown=1000:1000 --from=builder /out/dist/ ./dist/
COPY --chown=1000:1000 --from=builder /out/build ./dist/build/
COPY --chown=1000:1000 proxy.config.json config.schema.json ./
COPY docker-entrypoint.sh /docker-entrypoint.sh

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"build": "npm run generate-config-types && npm run build-ui && npm run build-ts",
"build-ts": "tsc --project tsconfig.publish.json && node scripts/fix-shebang.js",
"build-ui": "vite build",
"build-validate": "./scripts/build-validate.sh",
"check-types": "tsc",
"check-types:server": "tsc --project tsconfig.publish.json --noEmit",
"test-shuffle": "NODE_ENV=test vitest --run --dir ./test --sequence.shuffle",
Expand Down
77 changes: 77 additions & 0 deletions scripts/package-smoke-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail

CLEAN_BUILD=true
if [[ "$1" == "--no-clean-build" ]]; then
CLEAN_BUILD=false
fi

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORK="$(mktemp -d)"
SERVER_PID=""
cleanup() {
[[ -n "$SERVER_PID" ]] && kill "$SERVER_PID" 2>/dev/null || true
rm -rf "$WORK"
}
trap cleanup EXIT

cd "$ROOT"

if [[ "$CLEAN_BUILD" == true ]]; then
echo "Building from a clean slate..."
rm -rf dist build
npm run build
fi

TARBALL="$WORK/$(npm pack --silent --pack-destination "$WORK")"
echo "Packed: $TARBALL"
FILES="$(tar tzf "$TARBALL")"

# UI must be inside the tarball
if ! grep -qx 'package/dist/build/index.html' <<<"$FILES"; then
echo "FAIL: dist/build/index.html is missing from the tarball."
echo "--- everything under dist/build ---"
grep '^package/dist/build' <<<"$FILES" || echo "(dist/build is entirely absent)"
echo "--- top level ---"
sed 's|^package/||' <<<"$FILES" | cut -d/ -f1 | sort -u
exit 1
fi

grep -qE '^package/dist/build/assets/.+\.js$' <<<"$FILES" \
|| { echo "FAIL: no JS bundle under dist/build/assets."; exit 1; }

# Install like a normal user
cd "$WORK"
npm init -y >/dev/null
npm install --no-audit --no-fund --loglevel=error "$TARBALL"

# Boot from a scratch cwd so it writes its .data/.tmp
mkdir -p "$WORK/run" && cd "$WORK/run"
"$WORK/node_modules/.bin/git-proxy" > "$WORK/server.log" 2>&1 &
SERVER_PID=$!

for i in $(seq 1 60); do
curl -fsS http://localhost:8080/api/v1/healthcheck >/dev/null 2>&1 && break
if [[ $i -eq 60 ]]; then
echo "FAIL: server did not come up within 60s"
cat "$WORK/server.log"
exit 1
fi
sleep 1
done

# Check UI wrapper is served
HTML="$(curl -fsS http://localhost:8080/)"
if ! grep -q '<div id="root">' <<<"$HTML"; then
echo "FAIL: / did not return the GitProxy UI wrapper."
head -30 <<<"$HTML"
exit 1
fi

# The bundled reference must resolve, not just exist as a string
ASSET="$(grep -oE '/assets/[A-Za-z0-9._-]+\.js' <<<"$HTML" | head -1)"
[[ -n "$ASSET" ]] || { echo "FAIL: index.html references no JS bundle."; exit 1; }
curl -fsS -o /dev/null "http://localhost:8080$ASSET" \
|| { echo "FAIL: $ASSET 404s."; exit 1; }

echo "PASS: packaged UI installs and serves correctly ($ASSET)"
10 changes: 8 additions & 2 deletions src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import * as db from '../db';
import { Proxy } from '../proxy';
import routes from './routes';
import { configure } from './passport';
import { UI_BUILD_PATH } from './urls';

const limiter = rateLimit(config.getRateLimit());

Expand Down Expand Up @@ -138,7 +139,7 @@ async function createApp(proxy: Proxy): Promise<Express> {
// configuration of passport is async
// Before we can bind the routes - we need the passport strategy
const passport = await configure();
const absBuildPath = path.join(__dirname, '../../build');
const absBuildPath = UI_BUILD_PATH;
app.use(cors(corsOptions));
app.set('trust proxy', 1);
app.use(limiter);
Expand Down Expand Up @@ -177,9 +178,14 @@ async function createApp(proxy: Proxy): Promise<Express> {
app.use('/', routes(proxy));
app.use('/', express.static(absBuildPath));
app.get('/*path', (_req, res) => {
res.sendFile(path.join(`${absBuildPath}/index.html`));
res.sendFile(path.join(absBuildPath, 'index.html'));
});

if (!fs.existsSync(path.join(absBuildPath, 'index.html'))) {
console.error(
`UI build not found at ${absBuildPath}. The package may have been built or published incorrectly`,
);
}
return app;
}

Expand Down
14 changes: 14 additions & 0 deletions src/service/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/

import { Request } from 'express';
import path from 'path';
import fs from 'fs';

import * as config from '../config';

Expand Down Expand Up @@ -42,3 +44,15 @@ export const getServiceUIURL = (req: Request): string => {
)
);
};

function findPackageRoot(from: string = __dirname): string {
let dir = from;
for (;;) {
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
const parent = path.dirname(dir);
if (parent === dir) throw new Error('Could not locate GitProxy package root');
dir = parent;
}
}

export const UI_BUILD_PATH = path.join(findPackageRoot(), 'dist', 'build');
1 change: 0 additions & 1 deletion tsconfig.publish.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"experimental/**",
"plugins/**",
"./dist/**",
"src/ui/**",
"**/*.tsx",
"**/*.jsx",
"./src/context.js",
Expand Down
2 changes: 1 addition & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default ({ mode }: { mode: string }) => {
const apiTarget = `http://127.0.0.1:${apiPort}`;
return defineConfig({
build: {
outDir: 'build',
outDir: 'dist/build',
},
server: {
port: 3000,
Expand Down
Loading