Skip to content

Commit fe3f4be

Browse files
authored
feat: stabilize public http interface & slimmer docker (#882)
This pull request restructures how the frontend assets and OpenAPI specification are built, served, and referenced throughout the application. The changes standardize the output locations, improve Docker and CI integration, and update documentation and code references to reflect these new conventions. The most important changes are grouped below. **Frontend build and serving changes:** - The frontend build output is now placed in `frontend/dist` instead of `public/frontend`, and the app serves static assets from this new directory at the root path (`/`). This affects the Docker build, static file serving configuration, and bookmarklet logic. (`[[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L96-R96)`, `[[2]](diffhunk://#diff-f965f92b425fb2f75d38b491b2625fe21b8af20b7666217546bce8a42b198ea4R34-R35)`, `[[3]](diffhunk://#diff-f965f92b425fb2f75d38b491b2625fe21b8af20b7666217546bce8a42b198ea4R85-R88)`, `[[4]](diffhunk://#diff-f965f92b425fb2f75d38b491b2625fe21b8af20b7666217546bce8a42b198ea4L103-R110)`, `[[5]](diffhunk://#diff-97c6e2c429ec483132c584137a18a1ade2ff6c3f4f6485f4a83db604d0323d7cR5)`, `[[6]](diffhunk://#diff-97c6e2c429ec483132c584137a18a1ade2ff6c3f4f6485f4a83db604d0323d7cL22-R23)`, `[[7]](diffhunk://#diff-7252f6c43b3cf192bd50ce66192ddce81eca27815a4b99eb57366d25e3715f07L9-R9)`, `[[8]](diffhunk://#diff-1f633b86af4bf00bbb33177b2e1a51a960711d1bb9901ce853b34e53d6bd7d14L275-R281)`, `[[9]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L104-R104)`, `[[10]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L23-R23)`, `README.md`, `docs/README.md`) **OpenAPI specification changes:** - The OpenAPI YAML file is now generated to and served from `public/openapi.yaml` instead of `docs/api/v1/openapi.yaml`. All references, build scripts, and tests have been updated accordingly. The versioned API route `/api/v1/openapi.yaml` now redirects to `/openapi.yaml`. (`[[1]](diffhunk://#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52L105-R124)`, `[[2]](diffhunk://#diff-ee98e028c59b193d58fde56ab4daf54d43c486ae674e63d50ddf300b07943e0fL99-R106)`, `[[3]](diffhunk://#diff-74d6d27981dace9acd4886bdcdc2fb9776477f7b22557e92832eb4cf46fb98cdL18-R18)`, `[[4]](diffhunk://#diff-28a08a74a84f026c2e107160e332d32db8c8338eaf53cf6c7930f6ff00afe952L26-R26)`, `[[5]](diffhunk://#diff-28a08a74a84f026c2e107160e332d32db8c8338eaf53cf6c7930f6ff00afe952L58-L80)`, `[[6]](diffhunk://#diff-da6498268e99511d9ba0df3c13e439d10556a812881c9d03955b2ef7c6c1c655L14-R15)`, `[[7]](diffhunk://#diff-0f9368690552ac79a208dde6e3f09cf9f4e350e910d0de10c35dc2cc235479b1L85-R85)`, `[[8]](diffhunk://#diff-1f633b86af4bf00bbb33177b2e1a51a960711d1bb9901ce853b34e53d6bd7d14L56-R56)`, `[[9]](diffhunk://#diff-347625202f0133fd177c976e7499085afcda2d709947a82294435a16276bd228L12-R12)`, `[[10]](diffhunk://#diff-7725db5f3a9f3b853587e27b2462cf1b84ead50477855af692f72bb6e159cca5L452-L466)`, `[[11]](diffhunk://#diff-39eaafdff54bffed770574d0dd758902acc94553d5bb2fb5eab86903fc2efff5L117-R117)`, `[[12]](diffhunk://#diff-39eaafdff54bffed770574d0dd758902acc94553d5bb2fb5eab86903fc2efff5L142-R147)`, `[[13]](diffhunk://#diff-0b5ca119d2be595aa307d34512d9679e49186307ef94201e4b3dfa079aa89938L7-R12)`, `[[14]](diffhunk://#diff-0b5ca119d2be595aa307d34512d9679e49186307ef94201e4b3dfa079aa89938L58-R58)`, `README.md`, `docs/README.md`) **Continuous Integration and Docker improvements:** - The CI workflow now installs frontend dependencies and verifies both the OpenAPI spec and client generation in a single step using `make openapi-verify`. Node dependencies are cached for faster builds, and the Docker build uses the new frontend output path. (`[[1]](diffhunk://#diff-b803fcb7f17ed9235f1e5cb1fcd2f5d3b2838429d4368ae4c57ce4436577f03fR61-R69)`, `[[2]](diffhunk://#diff-b803fcb7f17ed9235f1e5cb1fcd2f5d3b2838429d4368ae4c57ce4436577f03fL86-L88)`, `[[3]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L96-R96)`, `.dockerignore`) **Documentation and tests:** - All documentation, helper text, and tests have been updated to match the new asset and OpenAPI spec locations, ensuring consistency and preventing confusion for contributors. (`README.md`, `docs/README.md`, `frontend/package.json`, `frontend/src/__tests__`, `spec/html2rss/web/api/v1_spec.rb`) **Miscellaneous:** - The frontend favicon is now referenced as `/favicon.ico` instead of an inline SVG. (`[frontend/index.htmlL11-R11](diffhunk://#diff-959f9cada636604db33bc1ba82fed347457dcb032c37ac195b4343c5f0ea6e72L11-R11)`)
1 parent ead6312 commit fe3f4be

25 files changed

Lines changed: 61 additions & 113 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ renovate.json
2020
spec/
2121
tmp/
2222
work/
23+
frontend/dist/

.github/workflows/ci.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,15 @@ jobs:
5858
uses: actions/setup-node@v4
5959
with:
6060
node-version-file: ".tool-versions"
61+
cache: npm
62+
cache-dependency-path: frontend/package-lock.json
63+
64+
- name: Install frontend dependencies for OpenAPI client verification
65+
run: npm ci
66+
working-directory: frontend
6167

62-
- name: Verify generated OpenAPI is up to date
63-
run: bundle exec rake openapi:verify
68+
- name: Verify generated OpenAPI spec and client are up to date
69+
run: make openapi-verify
6470

6571
- name: Lint OpenAPI contract
6672
run: make openapi-lint
@@ -83,9 +89,6 @@ jobs:
8389
- name: Install dependencies
8490
run: npm ci
8591

86-
- name: Verify generated OpenAPI client is up to date
87-
run: npm run openapi:verify
88-
8992
- name: Typecheck frontend
9093
run: npm run typecheck
9194

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
# Ignore frontend build output and tooling caches
4040
/frontend/dist/
4141
/frontend/node_modules/
42-
/public/frontend
4342
/frontend/playwright-report/
4443
/frontend/test-results/
4544

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,6 @@ COPY --chown=$USER:$USER Gemfile Gemfile.lock app.rb config.ru ./
9393
COPY --chown=$USER:$USER app ./app
9494
COPY --chown=$USER:$USER config ./config
9595
COPY --chown=$USER:$USER public ./public
96-
COPY --from=frontend-builder --chown=$USER:$USER /app/public/frontend ./public/frontend
96+
COPY --from=frontend-builder --chown=$USER:$USER /app/frontend/dist ./frontend/dist
9797

9898
CMD ["bundle", "exec", "puma", "-C", "./config/puma.rb"]

Makefile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,26 +102,26 @@ ready: ## Pre-commit gate (quick checks + RSpec)
102102
yard-verify-public-docs: ## Verify essential YARD docs for all public methods in app/
103103
bundle exec rake yard:verify_public_docs
104104

105-
openapi: ## Regenerate docs/api/v1/openapi.yaml from request specs
105+
openapi: ## Regenerate public/openapi.yaml from request specs
106106
bundle exec rake openapi:generate
107107

108-
openapi-verify: ## Regenerate OpenAPI and fail if docs/api/v1/openapi.yaml or frontend client is stale
108+
openapi-verify: ## Regenerate OpenAPI and fail if public/openapi.yaml or frontend client is stale
109109
bundle exec rake openapi:verify
110110
$(MAKE) openapi-client-verify
111111

112-
openapi-client: ## Generate frontend OpenAPI client/types from docs/api/v1/openapi.yaml
112+
openapi-client: ## Generate frontend OpenAPI client/types from public/openapi.yaml
113113
@cd frontend && npm run openapi:generate
114114

115115
openapi-client-verify: ## Generate frontend OpenAPI client and fail if generated files are stale
116116
@cd frontend && npm run openapi:verify
117117

118-
openapi-lint: openapi-lint-redocly openapi-lint-spectral ## Lint docs/api/v1/openapi.yaml with Redocly and Spectral
118+
openapi-lint: openapi-lint-redocly openapi-lint-spectral ## Lint public/openapi.yaml with Redocly and Spectral
119119

120120
openapi-lint-redocly: ## Lint OpenAPI using Redocly recommended rules
121-
npx --yes @redocly/cli lint --config .redocly.yaml docs/api/v1/openapi.yaml
121+
npx --yes @redocly/cli lint --config .redocly.yaml public/openapi.yaml
122122

123123
openapi-lint-spectral: ## Lint OpenAPI using Spectral OAS rules
124-
npx --yes @stoplight/spectral-cli lint --ruleset .spectral.yaml docs/api/v1/openapi.yaml
124+
npx --yes @stoplight/spectral-cli lint --ruleset .spectral.yaml public/openapi.yaml
125125

126126
openai-lint-spectral: openapi-lint-spectral ## Alias for openapi-lint-spectral
127127

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ html2rss-web converts arbitrary websites into RSS 2.0 feeds with a slim Ruby bac
2020
## Architecture
2121

2222
- **Backend:** Ruby + Roda, backed by the `html2rss` gem for extraction.
23-
- **Frontend:** Preact app built with Vite into `public/frontend`.
23+
- **Frontend:** Preact app built with Vite into `frontend/dist` and served at `/`.
2424
- **Distribution:** Docker Compose by default; other deployments require manual wiring.
2525
- [Project notes](docs/README.md)
2626

@@ -85,7 +85,7 @@ For contributors and AI agents changing backend structure, follow the rules in [
8585
| `make lint` | Run all linters. |
8686
| `make lintfix` | Auto-fix lint warnings where possible. |
8787
| `make yard-verify-public-docs` | Enforce typed YARD docs for public methods in `app/`. |
88-
| `make openapi` | Regenerate `docs/api/v1/openapi.yaml` from request specs. |
88+
| `make openapi` | Regenerate `public/openapi.yaml` from request specs. |
8989
| `make openapi-verify` | Regenerate + fail if OpenAPI file is stale. |
9090
| `make clean` | Remove build artefacts. |
9191

@@ -101,7 +101,7 @@ The OpenAPI file is generated from Ruby request specs only.
101101
| Command | Purpose |
102102
| ----------------------- | -------------------------------------------- |
103103
| `npm run dev` | Vite dev server with hot reload (port 4001). |
104-
| `npm run build` | Build static assets into `public/frontend`. |
104+
| `npm run build` | Build static assets into `frontend/dist/`. |
105105
| `npm run test:run` | Unit tests (Vitest). |
106106
| `npm run test:contract` | Contract tests with MSW. |
107107

Rakefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,14 @@ end
9696
namespace :openapi do
9797
desc 'Generate OpenAPI YAML from request specs'
9898
task :generate do
99-
FileUtils.mkdir_p('docs/api/v1')
100-
FileUtils.rm_f('docs/api/v1/openapi.yaml')
99+
FileUtils.mkdir_p('public')
100+
FileUtils.rm_f('public/openapi.yaml')
101101
sh({ 'OPENAPI' => '1' }, 'bundle exec rspec spec/html2rss/web/api/v1_spec.rb --order defined')
102102
end
103103

104104
desc 'Verify generated OpenAPI YAML is up to date'
105105
task verify: :generate do
106-
sh 'git diff --exit-code -- docs/api/v1/openapi.yaml'
106+
sh 'git diff --exit-code -- public/openapi.yaml'
107107
end
108108
end
109109

app.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class App < Roda
3131
</body>
3232
</html>
3333
HTML
34+
FRONTEND_DIST_PATH = 'frontend/dist'
35+
FRONTEND_INDEX_PATH = File.join(FRONTEND_DIST_PATH, 'index.html')
3436
def self.development? = EnvironmentValidator.development?
3537

3638
def development? = self.class.development?
@@ -80,6 +82,10 @@ def development? = self.class.development?
8082
}
8183

8284
plugin :json_parser
85+
plugin :static,
86+
['/assets'],
87+
root: FRONTEND_DIST_PATH,
88+
headers: { 'Cache-Control' => 'public, max-age=31536000, immutable' }
8389
plugin :public
8490
plugin :head
8591
plugin :not_allowed
@@ -100,9 +106,8 @@ def development? = self.class.development?
100106
private
101107

102108
def render_index_page(router)
103-
index_path = 'public/frontend/index.html'
104109
router.response['Content-Type'] = 'text/html'
105-
File.exist?(index_path) ? File.read(index_path) : FALLBACK_HTML
110+
File.exist?(FRONTEND_INDEX_PATH) ? File.read(FRONTEND_INDEX_PATH) : FALLBACK_HTML
106111
end
107112
end
108113
end

app/web/api/v1/root_metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def build(router)
1515
api: {
1616
name: 'html2rss-web API',
1717
description: 'RESTful API for converting websites to RSS feeds',
18-
openapi_url: "#{router.base_url}/api/v1/openapi.yaml"
18+
openapi_url: "#{router.base_url}/openapi.yaml"
1919
},
2020
instance: instance_payload(router)
2121
}

app/web/routes/api_v1/metadata_routes.rb

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def call(router)
2323
def mount_openapi_spec(router)
2424
router.on 'openapi.yaml' do
2525
router.get do
26-
router.response['Content-Type'] = 'application/yaml'
27-
openapi_spec_contents
26+
router.redirect '/openapi.yaml', 301
2827
end
2928
end
3029
end
@@ -55,29 +54,11 @@ def mount_root(router)
5554
end
5655
end
5756

58-
# @return [String]
59-
def openapi_spec_path
60-
File.expand_path('../../../../docs/api/v1/openapi.yaml', __dir__)
61-
end
62-
6357
# @param router [Roda::RodaRequest]
6458
# @return [String]
6559
def render_root_metadata(router)
6660
JSON.generate(Api::V1::Response.success(data: Api::V1::RootMetadata.build(router)))
6761
end
68-
69-
# @return [String]
70-
def openapi_spec_contents
71-
return File.read(openapi_spec_path) if File.exist?(openapi_spec_path)
72-
73-
<<~YAML
74-
openapi: 3.0.3
75-
info:
76-
title: html2rss-web API
77-
version: 1.0.0
78-
paths: {}
79-
YAML
80-
end
8162
end
8263
end
8364
end

0 commit comments

Comments
 (0)