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
12 changes: 0 additions & 12 deletions .cursor/rules/read-copilot-instructions.mdc

This file was deleted.

6 changes: 3 additions & 3 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Containers extension) or GitHub Codespaces and use that environment for all work
The devcontainer starts one service named `app` and exposes:

- **Port 4000:** Ruby app
- **Port 4001:** Astro dev server
- **Port 4001:** Vite dev server

The repo is mounted at `/workspace`. Bundler gems are cached in a Docker volume to speed up
future launches.
Expand All @@ -29,9 +29,9 @@ network access is available.
## Common commands (run inside the container)

```
make dev # Ruby + Astro
make dev # Ruby + Vite
make dev-ruby # Ruby only
make dev-frontend # Astro only
make dev-frontend # frontend only
make test # Ruby + frontend tests
make ready # RuboCop + RSpec (pre-commit gate)
```
Expand Down
46 changes: 29 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ jobs:

- uses: ruby/setup-ruby@v1
with:
ruby-version-file: ".tool-versions"
bundler-cache: true

- name: Run RuboCop
Expand All @@ -53,7 +52,6 @@ jobs:

- uses: ruby/setup-ruby@v1
with:
ruby-version-file: ".tool-versions"
bundler-cache: true

- name: Setup Node.js for OpenAPI lint tooling
Expand Down Expand Up @@ -106,42 +104,56 @@ jobs:
- name: Run frontend smoke test
run: npm run test:e2e

docker-test:
docker-build-smoke-image:
needs:
- hadolint
- ruby
- openapi
- frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Build Docker smoke image
run: docker build -t html2rss/web -f Dockerfile .

- name: Export Docker smoke image
run: docker save html2rss/web -o /tmp/html2rss-web-smoke-image.tar

- name: Upload Docker smoke image
uses: actions/upload-artifact@v4
with:
name: docker-smoke-image
path: /tmp/html2rss-web-smoke-image.tar
retention-days: 1

docker-test:
needs:
- docker-build-smoke-image
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
smoke_auto_source_enabled: ["false", "true"]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- uses: ruby/setup-ruby@v1
with:
ruby-version-file: ".tool-versions"
bundler-cache: true

- name: Setup Node.js for Docker smoke test
uses: actions/setup-node@v4
- name: Download Docker smoke image
uses: actions/download-artifact@v4
with:
node-version-file: ".tool-versions"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies
run: npm ci
working-directory: frontend
name: docker-smoke-image
path: /tmp

- name: Build frontend static assets
run: npm run build
working-directory: frontend
- name: Load Docker smoke image
run: docker load -i /tmp/html2rss-web-smoke-image.tar

- name: Run Docker smoke test
env:
DOCKER_SMOKE_SKIP_BUILD: "true"
SMOKE_AUTO_SOURCE_ENABLED: ${{ matrix.smoke_auto_source_enabled }}
run: bundle exec rake

Expand Down
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@
/tmp/rack-cache-*


# Ignore Astro frontend build output
# Ignore frontend build output and tooling caches
/frontend/dist/
/frontend/.astro/
/frontend/node_modules/
/public/frontend
/frontend/playwright-report/
Expand All @@ -51,4 +50,8 @@
# Frontend logs
*.log

# macOS Finder metadata
.DS_Store

.yardoc
frontend/.astro
5 changes: 5 additions & 0 deletions .redocly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extends:
- recommended

rules:
operation-4xx-response: off
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ AllCops:
- '**/*.yml'
- '**/*.yaml'
- '**/.tool-versions'
- 'coverage/**/*'
- 'frontend/node_modules/**/*'
- 'frontend/test-results/**/*'
- 'public/frontend/**/*'
- 'tmp/**/*'
- 'vendor/**/*'

Layout/LineLength:
Max: 120
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ ARG UID=991
ARG GID=991

RUN apk add --no-cache \
'ca-certificates>=2024' \
'curl>=8' \
'gcompat>=0' \
'tzdata>=2024' \
Expand Down
11 changes: 9 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,21 @@ task :test do
current_dir = ENV.fetch('GITHUB_WORKSPACE', __dir__)
smoke_auto_source_enabled = ENV.fetch('SMOKE_AUTO_SOURCE_ENABLED', 'false')
image_name = 'html2rss/web'
skip_build = ENV.fetch('DOCKER_SMOKE_SKIP_BUILD', 'false') == 'true'

if skip_build
Output.describe 'Running with prebuilt docker image'
else
Output.describe 'Building and running'
sh "docker build -t #{image_name} -f Dockerfile ."
end

Output.describe 'Building and running'
sh "docker build -t #{image_name} -f Dockerfile ."
sh ['docker run',
'-d',
'-p 4000:4000',
'--env PUMA_LOG_CONFIG=1',
'--env HEALTH_CHECK_TOKEN=CHANGE_ME_HEALTH_CHECK_TOKEN',
'--env HTML2RSS_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
"--env AUTO_SOURCE_ENABLED=#{smoke_auto_source_enabled}",
"--mount type=bind,source=#{current_dir}/config,target=/app/config",
'--name html2rss-web-test',
Expand Down
8 changes: 0 additions & 8 deletions app.json

This file was deleted.

18 changes: 9 additions & 9 deletions bin/dev-with-frontend
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export RACK_ENV=${RACK_ENV:-development}
echo "Starting html2rss-web development environment..."
echo "Environment: $RACK_ENV"
echo "Ruby server: http://localhost:4000"
echo "Astro dev server: http://localhost:4001 (with live reload)"
echo "Vite dev server: http://localhost:4001 (with live reload)"
echo "Main development URL: http://localhost:4001"
echo ""

Expand All @@ -24,10 +24,10 @@ cleanup() {
echo ""
echo "Shutting down servers..."
kill $RUBY_PID 2>/dev/null || true
kill $ASTRO_PID 2>/dev/null || true
kill $FRONTEND_PID 2>/dev/null || true
kill $WATCHER_PID 2>/dev/null || true
wait $RUBY_PID 2>/dev/null || true
wait $ASTRO_PID 2>/dev/null || true
wait $FRONTEND_PID 2>/dev/null || true
wait $WATCHER_PID 2>/dev/null || true
echo "Servers stopped."
exit 0
Expand All @@ -44,16 +44,16 @@ RUBY_PID=$!
# Wait a moment for Ruby server to start
sleep 3

# Start Astro dev server with API proxy
echo "Starting Astro dev server with API proxy..."
# Start frontend dev server with API proxy
echo "Starting frontend dev server with API proxy..."
cd frontend

# Start Astro dev server (it will proxy API calls to Ruby server)
# Start frontend dev server (it will proxy API calls to Ruby server)
npm run dev &
ASTRO_PID=$!
FRONTEND_PID=$!

# Wait a moment for Astro server to start
# Wait a moment for the frontend server to start
sleep 3

# Wait for both processes
wait $RUBY_PID $ASTRO_PID
wait $RUBY_PID $FRONTEND_PID
2 changes: 1 addition & 1 deletion bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ echo "Running tests to verify setup..."
bundle exec rspec

echo "Setup complete! You can now run:"
echo " bin/dev # Start development server (Ruby + Astro)"
echo " bin/dev # Start development server (Ruby + Vite)"
echo " bin/dev-ruby # Start Ruby server only"
echo " bundle exec rspec # Run tests"
echo " bundle exec rubocop # Run linter"
21 changes: 10 additions & 11 deletions docs/api/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ paths:
get:
description: API metadata
operationId: getApiMetadata
parameters: []
responses:
'200':
content:
Expand Down Expand Up @@ -66,7 +67,7 @@ paths:
- success
- data
type: object
description: returns instance feed-creation capability
description: returns API information with trailing slash
security: []
summary: API metadata
tags:
Expand Down Expand Up @@ -110,6 +111,8 @@ paths:
type: string
id:
type: string
json_public_url:
type: string
name:
type: string
public_url:
Expand All @@ -127,6 +130,7 @@ paths:
- strategy
- feed_token
- public_url
- json_public_url
- created_at
- updated_at
type: object
Expand Down Expand Up @@ -221,9 +225,7 @@ paths:
'401':
content:
application/feed+json:
example:
title: Error
version: https://jsonfeed.org/version/1.1
example: '{"version":"https://jsonfeed.org/version/1.1","title":"Error"}'
schema:
type: string
application/xml:
Expand All @@ -236,9 +238,7 @@ paths:
'403':
content:
application/feed+json:
example:
title: Error
version: https://jsonfeed.org/version/1.1
example: '{"version":"https://jsonfeed.org/version/1.1","title":"Error"}'
schema:
type: string
application/xml:
Expand All @@ -252,9 +252,7 @@ paths:
'500':
content:
application/feed+json:
example:
title: Error
version: https://jsonfeed.org/version/1.1
example: '{"version":"https://jsonfeed.org/version/1.1","title":"Error"}'
schema:
type: string
application/xml:
Expand Down Expand Up @@ -317,7 +315,8 @@ paths:
- success
- data
type: object
description: returns health status when token is valid
description: returns health status when the configured environment token
is valid
'401':
content:
application/json:
Expand Down
23 changes: 14 additions & 9 deletions frontend/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { expect, test } from '@playwright/test';

test.describe('frontend smoke', () => {
test('loads demo onboarding and auth toggle', async ({ page }) => {
test('loads create flow and inline access-token gate', async ({ page }) => {
await page.goto('/');

await expect(page.getByRole('heading', { name: 'Convert website to RSS' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Run demo' })).toBeVisible();
await expect(page.getByLabel('PAGE URL')).toBeVisible();
await expect(page.getByRole('button', { name: 'Generate feed URL' })).toBeVisible();
await expect(page.getByRole('button', { name: 'MORE' })).toBeVisible();

await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('button', { name: 'Back to demo' })).toBeVisible();
await expect(page.getByLabel('Username')).toBeVisible();
await expect(page.getByLabel('Token')).toBeVisible();
await page.getByLabel('PAGE URL').fill('https://example.com/articles');
await page.getByRole('button', { name: 'Generate feed URL' }).click();

await page.getByRole('button', { name: 'Back to demo' }).click();
await expect(page.getByRole('button', { name: 'Run demo' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Add access token' })).toBeVisible();
await expect(page.getByRole('textbox', { name: 'Access token' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Save and continue' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Back' })).toBeVisible();

await page.getByRole('button', { name: 'Back' }).click();
await expect(page.getByRole('button', { name: 'Generate feed URL' })).toBeVisible();
await expect(page.getByRole('button', { name: 'MORE' })).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"format:check": "prettier --check .",
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
"openapi:generate": "openapi-ts -i ../docs/api/v1/openapi.yaml -o src/api/generated -c @hey-api/client-fetch",
"openapi:verify": "npm run openapi:generate && git diff --exit-code -- src/api/generated",
"openapi:verify": "openapi-ts -i ../docs/api/v1/openapi.yaml -o src/api/generated -c @hey-api/client-fetch && git diff --exit-code -- src/api/generated",
"test": "vitest",
"test:run": "vitest run",
"test:unit": "vitest run --reporter=verbose --config vitest.unit.config.ts",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/__tests__/ResultDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('ResultDisplay', () => {
expect(screen.getByText(/points by canpan/i)).toBeInTheDocument();
expect(screen.getByText('Item Two')).toBeInTheDocument();
});
expect(window.fetch).toHaveBeenCalledWith('https://example.com/feed.json', {
expect(window.fetch).toHaveBeenCalledWith('https://example.com/feed.xml', {
headers: { Accept: 'application/feed+json' },
});
});
Expand Down
Loading
Loading