diff --git a/.github/workflows/fresh-install.yml b/.github/workflows/fresh-install.yml new file mode 100644 index 0000000..7893d2d --- /dev/null +++ b/.github/workflows/fresh-install.yml @@ -0,0 +1,72 @@ +# Installation neuve en suivant le README, sur une machine vierge : clone, .env, docker compose up -d, port 80. +# Lancé à la main (onglet Actions, « Fresh install », Run workflow). Les captures sont jointes au run. +name: Fresh install + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + fresh-install: + name: README self-hosting steps + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Clone, as in the README + run: | + git clone https://github.com/IliesAllali/parenthese.git + cd parenthese + git log -1 --oneline + + - name: .env, as in the README + working-directory: parenthese + run: | + set -euo pipefail + cp .env.example .env + # README : POSTGRES_PASSWORD en lettres et chiffres, JWT_SECRET par openssl rand -base64 48 + sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" .env + sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$(openssl rand -base64 48)|" .env + grep -Ev '^(POSTGRES_PASSWORD|JWT_SECRET)=' .env | grep -Ev '^\s*(#|$)' + + - name: docker compose up -d + working-directory: parenthese + run: docker compose up -d + + - name: Wait for http://localhost + run: | + for i in $(seq 1 90); do + if curl -fsS http://localhost/health | grep -Eq '"ok": ?true'; then echo "ready after $((i * 5)) s"; exit 0; fi + sleep 5 + done + echo "not ready after 450 s"; exit 1 + + - name: Smoke test through the web port + working-directory: parenthese + run: bash tools/docker-smoke.sh http://localhost + + - name: Screenshots + working-directory: parenthese + run: | + mkdir -p shots /tmp/pw + npm --prefix /tmp/pw init -y > /dev/null + npm --prefix /tmp/pw install playwright@1 > /dev/null + (cd /tmp/pw && npx playwright install --with-deps chromium > /dev/null) + cp tools/fresh-install-shots.mjs /tmp/pw/shots.mjs + node /tmp/pw/shots.mjs http://localhost "$PWD/shots" + + - name: Upload screenshots + if: always() + uses: actions/upload-artifact@v4 + with: + name: fresh-install-shots + path: parenthese/shots + if-no-files-found: warn + + - name: Containers and logs on failure + if: failure() + working-directory: parenthese + run: | + docker compose ps -a + docker compose logs --no-color --timestamps diff --git a/tools/fresh-install-shots.mjs b/tools/fresh-install-shots.mjs new file mode 100644 index 0000000..75f11ee --- /dev/null +++ b/tools/fresh-install-shots.mjs @@ -0,0 +1,36 @@ +// Captures d'une instance fraîchement installée, pour vérifier à l'œil que l'installation du README marche. +// Lancé par le workflow « Fresh install » : node tools/fresh-install-shots.mjs +// Chaque étape est capturée même si la suivante échoue ; le script sort en erreur si une étape a échoué. +import { chromium } from 'playwright' + +const [base = 'http://localhost', out = 'shots'] = process.argv.slice(2) +const browser = await chromium.launch({ args: ['--lang=en-US'] }) +const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, locale: 'en-US' }) +const errors = [] +page.on('pageerror', (error) => errors.push(`page error: ${error.message}`)) + +async function step(name, action) { + try { + await action() + } catch (error) { + errors.push(`${name}: ${error.message.split('\n')[0]}`) + } + await page.waitForTimeout(1500) + await page.screenshot({ path: `${out}/${name}.png` }) +} + +const stamp = Date.now().toString(36) + +await step('1-demo', () => page.goto(`${base}/?lang=en`, { waitUntil: 'networkidle' })) +await step('2-register', async () => { + await page.goto(`${base}/?account=register&lang=en`, { waitUntil: 'networkidle' }) + await page.getByLabel('First name', { exact: true }).fill('Camille') + await page.getByLabel('Email', { exact: true }).fill(`fresh-${stamp}@example.com`) + await page.getByLabel('Password', { exact: true }).fill(`fresh-install-${stamp}`) +}) +await step('3-after-register', () => page.getByRole('button', { name: 'Create my account' }).click()) +await step('4-reload', () => page.reload({ waitUntil: 'networkidle' })) + +console.log(errors.length ? errors.join('\n') : 'all steps ran') +await browser.close() +if (errors.length) process.exit(1)