Skip to content
Merged
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
10 changes: 9 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@ jobs:
- name: Build application
run: pnpm build
env:
GITHUB_ACTIONS: false
# The E2E suite serves this build from the ROOT (`serve out -l 3000`).
# DISABLE_BASE_PATH forces a basePath-less build so /_next/*.js assets
# resolve at /. If the build baked in the GitHub Pages basePath
# (/ScriptHammer), every /ScriptHammer/_next/*.js would 404 under
# serve-at-root, React would never hydrate, and the whole suite fails.
# A step-level `GITHUB_ACTIONS: false` is NOT reliable here — the
# runner re-injects GITHUB_ACTIONS=true into the child processes
# `pnpm build` spawns (scripts/detect-project.js / next.config execSync).
DISABLE_BASE_PATH: 'true'
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY: ${{ secrets.EMAILJS_PUBLIC_KEY }}
NEXT_PUBLIC_EMAILJS_SERVICE_ID: ${{ vars.NEXT_PUBLIC_EMAILJS_SERVICE_ID }}
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID: ${{ vars.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID }}
Expand Down
101 changes: 101 additions & 0 deletions scripts/__tests__/detect-project.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,107 @@ export type DetectedConfig = typeof detectedConfig;
assert.strictEqual(config.projectOwner, 'EnvOwner');
});

// Mirrors the basePath resolution in detect-project.js:
// basePathDisabled ? '' : NEXT_PUBLIC_BASE_PATH || (isGitHubActions && isGitHub && !cnameExists ? `/${name}` : '')
const resolveBasePath = ({
disableBasePath,
envBasePath,
isGitHubActions,
isGitHub,
cnameExists,
projectName,
}) =>
disableBasePath
? ''
: envBasePath ||
(isGitHubActions && isGitHub && !cnameExists
? `/${projectName}`
: '');

test('DISABLE_BASE_PATH forces an empty base path even in GitHub Actions', () => {
// Without the disable flag, a project-site build (GHA, GitHub repo, no
// CNAME) would auto-detect the /RepoName prefix.
assert.strictEqual(
resolveBasePath({
disableBasePath: false,
envBasePath: '',
isGitHubActions: true,
isGitHub: true,
cnameExists: false,
projectName: 'ScriptHammer',
}),
'/ScriptHammer'
);

// DISABLE_BASE_PATH=true wins over the auto-detection so the E2E build
// serves from the root.
assert.strictEqual(
resolveBasePath({
disableBasePath: true,
envBasePath: '',
isGitHubActions: true,
isGitHub: true,
cnameExists: false,
projectName: 'ScriptHammer',
}),
''
);

// The disable flag even overrides an explicit NEXT_PUBLIC_BASE_PATH.
assert.strictEqual(
resolveBasePath({
disableBasePath: true,
envBasePath: '/ScriptHammer',
isGitHubActions: true,
isGitHub: true,
cnameExists: false,
projectName: 'ScriptHammer',
}),
''
);
});

test('base path resolution is unchanged when DISABLE_BASE_PATH is unset', () => {
// A custom-domain deploy (CNAME present) resolves to '' — the template case.
assert.strictEqual(
resolveBasePath({
disableBasePath: false,
envBasePath: '',
isGitHubActions: true,
isGitHub: true,
cnameExists: true,
projectName: 'ScriptHammer',
}),
''
);

// An explicit env var still wins over auto-detection.
assert.strictEqual(
resolveBasePath({
disableBasePath: false,
envBasePath: '/custom',
isGitHubActions: true,
isGitHub: true,
cnameExists: false,
projectName: 'ScriptHammer',
}),
'/custom'
);

// Outside GitHub Actions there is no base path.
assert.strictEqual(
resolveBasePath({
disableBasePath: false,
envBasePath: '',
isGitHubActions: false,
isGitHub: true,
cnameExists: false,
projectName: 'ScriptHammer',
}),
''
);
});

test('should handle concurrent file writes', () => {
// This tests that file writes won't interfere with each other
const paths = ['/tmp/test1.json', '/tmp/test2.json', '/tmp/test3.json'];
Expand Down
24 changes: 18 additions & 6 deletions scripts/detect-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,29 @@ function generateConfig() {
// Determine if we're in GitHub Actions CI/CD
const isGitHubActions = process.env.GITHUB_ACTIONS === 'true';

// Explicit opt-out for jobs that serve the static export from the ROOT
// (e.g. the E2E workflow's `serve out -l 3000`). Without this, the build
// bakes in the GitHub Pages basePath (/RepoName) and every
// /RepoName/_next/*.js asset 404s when served at /, so React never hydrates
// and the whole E2E suite fails. A step-level `GITHUB_ACTIONS: false` is NOT
// reliable — the runner re-injects GITHUB_ACTIONS=true into the child
// processes spawned by `pnpm build` / next.config's execSync — so we need an
// unambiguous, dedicated signal that survives that.
const basePathDisabled = process.env.DISABLE_BASE_PATH === 'true';

// Check if using custom domain (CNAME file exists)
const cnameExists = fs.existsSync(
path.join(__dirname, '..', 'public', 'CNAME')
);

// Base path: prefer explicit env var, fall back to auto-detection in GitHub Actions
const basePath =
process.env.NEXT_PUBLIC_BASE_PATH ||
(isGitHubActions && info.isGitHub && !cnameExists
? `/${info.projectName}`
: '');
// Base path: explicit disable wins; then explicit env var; then
// auto-detection for the GitHub Pages deploy build.
const basePath = basePathDisabled
? ''
: process.env.NEXT_PUBLIC_BASE_PATH ||
(isGitHubActions && info.isGitHub && !cnameExists
? `/${info.projectName}`
: '');

const config = {
projectName: info.projectName,
Expand Down
Loading