Skip to content

feat(api/baseline): add web-features/baseline data endpoint#498

Merged
marcoscaceres merged 9 commits into
mainfrom
feat/baseline-endpoint
May 3, 2026
Merged

feat(api/baseline): add web-features/baseline data endpoint#498
marcoscaceres merged 9 commits into
mainfrom
feat/baseline-endpoint

Conversation

@marcoscaceres

Copy link
Copy Markdown
Collaborator

Summary

  • GET /api/baseline — full web-features dataset
  • GET /api/baseline/:feature — single feature lookup (follows moved/split redirects)
  • POST /api/baseline/search — spec-URL-based feature matching
  • In-memory store indexed by feature ID and spec URL
  • Scraper fetches from web-platform-dx/web-features GitHub releases

Closes #490

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new /api/baseline API surface that serves the web-features “Baseline” dataset from a local on-disk cache, with endpoints for full dataset retrieval, single-feature lookup (including moved/split handling), spec-URL search, and a webhook-triggered background refresh flow.

Changes:

  • Introduces GET /api/baseline and GET /api/baseline/:feature routes backed by an in-memory store loaded from DATA_DIR/baseline/baseline.json.
  • Adds POST /api/baseline/search for spec-URL-based feature matching using a spec→feature index.
  • Implements a GitHub-releases-based scraper plus /api/baseline/update background worker integration, and wires the router + update script + env secret.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/update-data-sources.ts Runs the baseline scraper as part of the existing data-source refresh script.
routes/api/baseline/update.worker.ts Worker entrypoint that invokes the baseline scraper in a background thread.
routes/api/baseline/update.ts Webhook route that enqueues the background update job and reloads the store.
routes/api/baseline/search.post.ts Implements POST search by spec URLs against the in-memory spec index.
routes/api/baseline/lib/store.ts Defines dataset types, URL normalization, and builds in-memory indexes from baseline.json.
routes/api/baseline/lib/store-init.ts Initializes a singleton BaselineStore used by the handlers.
routes/api/baseline/lib/scraper.ts Downloads data.json from the latest web-features GitHub release into DATA_DIR.
routes/api/baseline/index.ts Express router wiring (CORS, JSON body parsing, webhook auth).
routes/api/baseline/feature.ts Implements single-feature lookup with moved/split resolution behavior.
routes/api/baseline/all.ts Implements full dataset retrieval.
app.ts Mounts the new router at /api/baseline.
.env.sample Adds WEB_FEATURES_SECRET for webhook authentication configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/search.post.ts
Comment thread routes/api/baseline/search.post.ts Outdated
Comment thread routes/api/baseline/update.ts
Comment thread routes/api/baseline/search.post.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/lib/store.ts Outdated
Comment thread routes/api/baseline/lib/store.ts Outdated
Comment thread routes/api/baseline/lib/store.ts Outdated
Comment thread routes/api/baseline/lib/scraper.ts Outdated
Comment thread routes/api/baseline/all.ts
Comment thread routes/api/baseline/search.post.ts
Adds endpoints for serving Baseline browser availability data:
- GET /api/baseline — full web-features dataset
- GET /api/baseline/:feature — single feature lookup
- POST /api/baseline/search — spec-URL-based feature search

Data sourced from web-platform-dx/web-features GitHub releases.
Enables ReSpec's implementationStatus feature (speced/respec#5174)
without requiring unpkg.com dependency.

Closes #490
Client expects {result: WebFeatureEntry[]}, server was returning
Record<string, {id, feature}[]>. Flatten features, deduplicate by
ID, and wrap in {result: [...]}.
@marcoscaceres
marcoscaceres force-pushed the feat/baseline-endpoint branch from 5381c80 to a8d58be Compare April 26, 2026 00:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/search.post.ts
Comment thread routes/api/baseline/search.post.ts Outdated
Comment thread routes/api/baseline/feature.ts
Comment thread routes/api/baseline/lib/store.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/search.post.ts Outdated
Comment thread routes/api/baseline/feature.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/search.post.ts
Comment thread routes/api/baseline/search.post.ts Outdated
Comment thread routes/api/baseline/lib/store.ts Outdated
Comment thread routes/api/baseline/lib/scraper.ts Outdated
Comment thread routes/api/baseline/lib/scraper.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/api/baseline/lib/scraper.ts Outdated
Comment thread routes/api/baseline/lib/store.ts Outdated
Comment thread routes/api/baseline/search.post.ts
@marcoscaceres
marcoscaceres requested a review from Copilot April 26, 2026 04:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

marcoscaceres and others added 2 commits April 27, 2026 18:30
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@marcoscaceres

Copy link
Copy Markdown
Collaborator Author

@copilot can you fix any unaddressed comments

Copilot AI added 2 commits April 27, 2026 08:43
- search.post.ts: return 503 when store.data is null (service unavailable)
- search.post.ts: reject empty/whitespace-only strings in specs array
- store.ts: normalizeUrl() now also clears search params (query string)
- scraper.ts: add ETag/If-None-Match support to skip unnecessary re-downloads
- scraper.ts: use atomic write (temp file + rename) to prevent corrupt data on interrupted writes
- Add tests: normalizeUrl, feature route (moved/split/404), search.post validation

Agent-Logs-Url: https://github.com/speced/respec-web-services/sessions/41823caf-5a5e-40fc-8bf7-fca753fb37ab

Copilot AI commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@copilot can you fix any unaddressed comments

Fixed all unresolved comments in commits 9e87c3a and 1e3cafc:

  • search.post.ts: returns 503 when store.data is null (instead of 200 with empty result), and now rejects empty/whitespace-only strings in the specs array
  • store.ts: normalizeUrl() now also clears the query string (search params) in addition to the hash
  • scraper.ts: added ETag/If-None-Match conditional request support (skips re-download when data is unchanged, returns false); writes are now atomic via temp file + rename; replaced existsSync + readFile with a try/catch to eliminate the race condition
  • Tests: added Jasmine specs covering normalizeUrl (hash, query, trailing-slash), feature.ts route (moved→target, split→targets, 404, no data), and search.post.ts validation (503 on no data, 400 on empty/whitespace/non-string specs)

@marcoscaceres

Copy link
Copy Markdown
Collaborator Author

@copilot Can you review this and fix any issues that come up?

@marcoscaceres

Copy link
Copy Markdown
Collaborator Author

@copilot Can you confirm that all your comments have been addressed? Please resolve them if you’re happy. Can you fix anything new you see or outstanding from your feedback that hasn’t been addressed?

…tions, fix XSS

- Return 400 (not 200) for non-published webhook actions, consistent with
  caniuse/update.ts and respec/builds/update.ts patterns
- Set res.locals.reason = "action-not-published" for structured logging
- Use a static message instead of echoing req.body.action to avoid
  reflected XSS (CodeQL js/reflected-xss)

Agent-Logs-Url: https://github.com/speced/respec-web-services/sessions/5af37242-1fe0-4bdd-b54b-c45ab0c4120f

Copilot AI commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@copilot Can you confirm that all your comments have been addressed? Please resolve them if you’re happy. Can you fix anything new you see or outstanding from your feedback that hasn’t been addres...

All comments are addressed. The one remaining open thread (update.ts webhook action filtering) was already handled in a previous commit, but I also found and fixed two additional issues in f450113:

  1. update.ts status code: Changed the response for non-"published" webhook actions from 200 to 400, and added res.locals.reason = "action-not-published" — matching the caniuse and respec/builds route patterns.

  2. Reflected XSS: The previous message echoed req.body.action back verbatim (Webhook action "${req.body.action}" ignored.), which CodeQL flagged as js/reflected-xss. Replaced with a static string.

All 62 specs pass, CodeQL is clean.

@copilot Can you review this and fix any issues that come up?

Reviewed and fixed in f450113:

  • update.ts was returning 200 for non-"published" webhook actions — changed to 400 with res.locals.reason = "action-not-published", consistent with caniuse/update.ts and respec/builds/update.ts
  • The error message was echoing req.body.action (user input), which is a reflected XSS risk — replaced with a static string

Everything else was already clean: 503 on missing data, whitespace-spec rejection, normalizeUrl stripping search params, ETag-based conditional downloads, atomic scraper writes, and full test coverage.

@marcoscaceres
marcoscaceres marked this pull request as ready for review April 27, 2026 20:27
}

// Check if it's a moved or split feature in the raw data
const rawFeature = store.data?.features[featureId];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if featureId is "constructor" here?

"baseline_update",
);

export default async function route(req: Request, res: Response) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires setting up a webhook in that repo. Do we know someone who can do it for us?

Comment on lines +58 to +62
afterEach(() => {
store.data = null;
store.byFeature = new Map();
store.bySpecUrl = new Map();
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care to cleanup if beforeEach is setting data anyway?

@marcoscaceres
marcoscaceres merged commit 0093a2c into main May 3, 2026
3 checks passed
@marcoscaceres
marcoscaceres deleted the feat/baseline-endpoint branch May 3, 2026 08:34
marcoscaceres added a commit that referenced this pull request May 4, 2026
* feat(api/baseline): add web-features/baseline data endpoint

Adds endpoints for serving Baseline browser availability data:
- GET /api/baseline — full web-features dataset
- GET /api/baseline/:feature — single feature lookup
- POST /api/baseline/search — spec-URL-based feature search

Data sourced from web-platform-dx/web-features GitHub releases.
Enables ReSpec's implementationStatus feature (speced/respec#5174)
without requiring unpkg.com dependency.

Closes #490

* fix(api/baseline): return flat {result: [...]} from search endpoint

Client expects {result: WebFeatureEntry[]}, server was returning
Record<string, {id, feature}[]>. Flatten features, deduplicate by
ID, and wrap in {result: [...]}.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(api/baseline): address unresolved review comments

- search.post.ts: return 503 when store.data is null (service unavailable)
- search.post.ts: reject empty/whitespace-only strings in specs array
- store.ts: normalizeUrl() now also clears search params (query string)
- scraper.ts: add ETag/If-None-Match support to skip unnecessary re-downloads
- scraper.ts: use atomic write (temp file + rename) to prevent corrupt data on interrupted writes
- Add tests: normalizeUrl, feature route (moved/split/404), search.post validation

Agent-Logs-Url: https://github.com/speced/respec-web-services/sessions/41823caf-5a5e-40fc-8bf7-fca753fb37ab

* fix(api/baseline): replace existsSync with try-catch for ETag read, add return type doc

Agent-Logs-Url: https://github.com/speced/respec-web-services/sessions/41823caf-5a5e-40fc-8bf7-fca753fb37ab

* fix(api/baseline): update.ts returns 400 for non-published webhook actions, fix XSS

- Return 400 (not 200) for non-published webhook actions, consistent with
  caniuse/update.ts and respec/builds/update.ts patterns
- Set res.locals.reason = "action-not-published" for structured logging
- Use a static message instead of echoing req.body.action to avoid
  reflected XSS (CodeQL js/reflected-xss)

Agent-Logs-Url: https://github.com/speced/respec-web-services/sessions/5af37242-1fe0-4bdd-b54b-c45ab0c4120f

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add web-features/baseline data endpoint for implementationStatus

4 participants