1
0

deploy workflow
Some checks failed
Publish Pages / publish (push) Failing after 7s
Test / test (push) Has been cancelled

This commit is contained in:
Aiden
2026-06-11 16:05:20 +10:00
parent 731ee4e647
commit c86490542d
5 changed files with 202 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
name: Publish Pages
on:
push:
branches: [main]
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
env:
R2_ENDPOINT: https://${{ secrets.F40_PAGES_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
R2_ACCOUNT_ID: ${{ secrets.F40_PAGES_R2_ACCOUNT_ID }}
R2_BUCKET: ${{ secrets.F40_PAGES_R2_BUCKET }}
SITE_NAME_OVERRIDE: ${{ vars.F40_PAGES_SITE_NAME }}
BUILD_COMMAND_OVERRIDE: ${{ vars.F40_PAGES_BUILD_COMMAND }}
OUTPUT_DIR_OVERRIDE: ${{ vars.F40_PAGES_OUTPUT_DIR }}
DEFAULT_BUILD_COMMAND: ${{ vars.F40_PAGES_DEFAULT_BUILD_COMMAND }}
DEFAULT_OUTPUT_DIR: ${{ vars.F40_PAGES_DEFAULT_OUTPUT_DIR }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Resolve settings
run: |
SITE_NAME="${SITE_NAME_OVERRIDE:-${GITHUB_REPOSITORY##*/}}"
BUILD_COMMAND="${BUILD_COMMAND_OVERRIDE:-${DEFAULT_BUILD_COMMAND:-npm ci && npm run build:test-app}}"
OUTPUT_DIR="${OUTPUT_DIR_OVERRIDE:-${DEFAULT_OUTPUT_DIR:-dist}}"
echo "SITE_NAME=$SITE_NAME" >> "$GITHUB_ENV"
echo "BUILD_COMMAND=$BUILD_COMMAND" >> "$GITHUB_ENV"
echo "OUTPUT_DIR=$OUTPUT_DIR" >> "$GITHUB_ENV"
- name: Validate publish settings
env:
AWS_ACCESS_KEY_ID: ${{ secrets.F40_PAGES_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.F40_PAGES_R2_SECRET_ACCESS_KEY }}
run: |
missing=0
for name in R2_ACCOUNT_ID R2_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do
value="$(eval "printf '%s' \"\${$name:-}\"")"
if [ -z "$value" ]; then
echo "::error::$name is required"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi
case "$SITE_NAME" in
""|*[!A-Za-z0-9._-]*)
echo "::error::SITE_NAME must contain only letters, numbers, '.', '_', and '-'"
exit 1
;;
esac
- name: Install AWS CLI
run: |
if ! command -v aws >/dev/null 2>&1; then
python3 -m pip install --user awscli
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
fi
- name: Build static site
run: sh -c "$BUILD_COMMAND"
- name: Validate build output
run: |
if [ ! -d "$OUTPUT_DIR" ]; then
echo "::error::Build output directory '$OUTPUT_DIR' does not exist"
exit 1
fi
if [ ! -f "$OUTPUT_DIR/index.html" ]; then
echo "::warning::'$OUTPUT_DIR/index.html' does not exist; /$SITE_NAME/ will return 404"
fi
- name: Publish to R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.F40_PAGES_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.F40_PAGES_R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
run: |
RUN_ATTEMPT="${GITHUB_RUN_ATTEMPT:-1}"
RELEASE="${GITHUB_SHA}-${RUN_ATTEMPT}"
PREFIX="sites/${SITE_NAME}/releases/${RELEASE}"
PUBLISHED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
aws s3 sync "$OUTPUT_DIR/" "s3://${R2_BUCKET}/${PREFIX}/" \
--endpoint-url "$R2_ENDPOINT" \
--delete \
--cache-control "public,max-age=31536000,immutable"
printf '{"site":"%s","release":"%s","sha":"%s","publishedAt":"%s"}\n' \
"$SITE_NAME" "$RELEASE" "$GITHUB_SHA" "$PUBLISHED_AT" > current.json
aws s3 cp current.json "s3://${R2_BUCKET}/sites/${SITE_NAME}/current.json" \
--endpoint-url "$R2_ENDPOINT" \
--content-type application/json \
--cache-control no-store

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
node_modules/
.env.r2
dist/
# Generated by `npm run build`.
vr180player/*.css

View File

@@ -106,6 +106,43 @@ This builds the TypeScript player once, then serves `index.html` with Vite at a
For headset testing, the page must be a secure context before the browser will expose immersive WebXR. A LAN URL such as `http://192.168.x.x:5173/` is useful for checking layout and media loading, but it will usually not show the headset's immersive VR prompt. Use an HTTPS URL with a trusted certificate, a trusted tunnel, or a deployed CDN/Pages URL for immersive testing.
## F40 Pages deploy
This repo includes a Gitea Actions workflow at `.gitea/workflows/publish-pages.yml`. It builds the test app into `dist/` and uploads it to the shared F40 Pages R2 bucket using the layout:
```txt
sites/{site-name}/releases/{git-sha}-{run-attempt}/
sites/{site-name}/current.json
```
By default, this repo uses:
```sh
npm ci && npm run build:test-app
```
The `build:test-app` script compiles the player, copies the generated `vr180player/` CDN assets, copies the simplified `test-pages/` app, and optionally copies `media/` if that local directory exists.
Required Gitea secrets:
- `F40_PAGES_R2_ACCOUNT_ID`
- `F40_PAGES_R2_BUCKET`
- `F40_PAGES_R2_ACCESS_KEY_ID`
- `F40_PAGES_R2_SECRET_ACCESS_KEY`
Optional Gitea variables:
- `F40_PAGES_SITE_NAME`: defaults to the repository name.
- `F40_PAGES_BUILD_COMMAND`: defaults to `npm ci && npm run build:test-app`.
- `F40_PAGES_OUTPUT_DIR`: defaults to `dist`.
When the middleman router serves the current release at `https://pages.f-40.com/{site-name}/`, the generated player can also be used as a CDN from:
```html
<script type="module" src="https://pages.f-40.com/VR-Web-Player/vr180player/vr180-player.js"></script>
```
For stronger CDN caching, the router can expose immutable release URLs and use the `current.json` file only to resolve the latest release.
## Development
The player source is TypeScript in `src/vr180player/`. Generated JavaScript files in `vr180player/` are ignored by git so CI/CD can build and publish them from source.
@@ -113,6 +150,7 @@ The player source is TypeScript in `src/vr180player/`. Generated JavaScript file
npm install
npm run dev
npm run build
npm run build:test-app
```
Edit the TypeScript source files rather than generated JavaScript. A typical CI/CD publish step should run `npm ci`, `npm run build`, then publish `vr180player/` with its generated `.js` files and CSS.

View File

@@ -6,6 +6,7 @@
"scripts": {
"dev": "npm run build && vite --host 0.0.0.0",
"build": "tsc && node scripts/copy-styles.mjs",
"build:test-app": "npm run build && node scripts/build-test-app.mjs",
"check": "tsc --noEmit",
"deploy:r2": "npm run build && npm run upload:r2",
"test": "npm run build && node --test tests/time.test.mjs tests/projection.test.mjs tests/media-controller.test.mjs tests/texture-manager.test.mjs tests/media-adapter.test.mjs tests/hand-aim.test.mjs tests/input-mode.test.mjs tests/icons.test.mjs tests/control-panel-timing.test.mjs tests/launcher.test.mjs",

View File

@@ -0,0 +1,58 @@
import { cp, mkdir, rm, stat, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..');
const distDir = join(rootDir, 'dist');
await rm(distDir, { force: true, recursive: true });
await mkdir(distDir, { recursive: true });
await copyRequired('index.html');
await copyRequired('poster.jpg');
await copyRequired('test-pages');
await copyRequired('vr180player');
await copyOptional('media');
await writeFile(
join(distDir, '_headers'),
[
'/vr180player/*',
' Cache-Control: public, max-age=3600',
'',
'/*',
' Cache-Control: public, max-age=300',
''
].join('\n')
);
console.log(`Built test app in ${distDir}`);
async function copyRequired(relativePath) {
const source = join(rootDir, relativePath);
const target = join(distDir, relativePath);
await cp(source, target, { recursive: true });
}
async function copyOptional(relativePath) {
const source = join(rootDir, relativePath);
if (!await pathExists(source)) {
console.warn(`Optional ${relativePath}/ directory not found; bundled sample media will not be included.`);
return;
}
await cp(source, join(distDir, relativePath), { recursive: true });
}
async function pathExists(path) {
try {
await stat(path);
return true;
} catch (error) {
if (error?.code === 'ENOENT') {
return false;
}
throw error;
}
}