feat(project-engine-client): foundation — vendor spec + generation pipeline (LLMO-5461)#1660
Conversation
…peline Create @adobe/spacecat-shared-project-engine-client and wire the Swagger 2.0 → type-generation pipeline for the Semrush Project Engine API. - Vendor projectengine_swagger_public.yaml (manual refresh; no live spec access) - spec:convert (swagger2openapi --patch) → generate:ts (openapi-typescript) → generate:pydantic (datamodel-codegen, pydantic v2); `generate` runs all three - mock script: Counterfact off the raw v2 spec (no conversion on the mock path) - JS + ESM + JSDoc, mocha/chai/c8, eslint-helix — matches spacecat-shared - linguist-generated markers + gitignore for build/ intermediate - smoke test asserts vendored spec + generated types presence datamodel-codegen emits a package dir (model.py/aiseo.py/http_server.py) rather than a single models.py: the spec's Go-style dotted schema names are modular references. Tool-flag layout choice; the spec is unmodified. The client wrapper, IMS handler move, and stateful mock land in follow-ups. Refs: LLMO-5461, LLMO-5459 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… ci] Generated by `npm run generate` from spec/projectengine_swagger_public.yaml: - src/generated/types.ts (openapi-typescript) - python/serenity_project_engine/ (datamodel-codegen, pydantic v2) Committed separately and marked linguist-generated (see .gitattributes) so the diff is collapsed in review. Do not hand-edit — re-run `npm run generate`. Refs: LLMO-5461 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
This PR will trigger a minor release when merged. |
- mark package private so semantic-release skips the npm publish on merge; a new package cannot use OIDC for its first publish (see scripts/setup-npm-trusted-publishers.sh), so leaving it publishable would fail the main release job. Publish + trust binding will be set up deliberately when the client lands. - declare a `types` entry pointing at the generated types so the package has a defined type surface (full main/exports arrives with the client wrapper). - fix `clean` script: no per-package lockfile exists in this monorepo; clean the dirs this package actually generates (build/, .counterfact/). - correct stale .gitignore comment: the generated Python package (model.py, aiseo.py, http_server.py, __init__.py) is committed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| "version": "1.0.0", | ||
| "description": "Shared modules of the Spacecat Services - Semrush Project Engine client and generated types", | ||
| "type": "module", | ||
| "private": true, |
There was a problem hiding this comment.
Must fix — first merge would fail the release job.
This monorepo's release job runs npx -ws semantic-release on every merge to main and publishes each package via OIDC Trusted Publishing (no npm token, NPM_CONFIG_PROVENANCE=true). Per scripts/setup-npm-trusted-publishers.sh, a new package cannot use OIDC for its first publish — it needs a manual npm publish as adobe-bot plus a trust binding, and this package isn't in that script's PACKAGES array. As written, the first merge would attempt an OIDC publish of @adobe/spacecat-shared-project-engine-client@1.0.0 and turn the release job red.
There was a problem hiding this comment.
Fixed in e440235 — marked the package "private": true, so @semantic-release/npm auto-skips publishing (the release job still versions/tags it, just no npm push), and the SR_NO_NPM_AUTH guard in .releaserc.cjs stays satisfied.
When the client lands and we actually want this on npm, flip private off and do the deliberate first publish: npm publish --access public as adobe-bot, add the name to the PACKAGES array, then re-run setup-npm-trusted-publishers.sh.
| "description": "Shared modules of the Spacecat Services - Semrush Project Engine client and generated types", | ||
| "type": "module", | ||
| "private": true, | ||
| "types": "./src/generated/types.ts", |
There was a problem hiding this comment.
Should fix — no entry point. Sibling packages (e.g. spacecat-shared-utils) declare main + an exports map; this one declared neither, so even the generated types weren't reachable through the package name.
There was a problem hiding this comment.
Fixed in e440235 — added a types entry pointing at the generated src/generated/types.ts, giving the package a defined type surface.
Deliberately held off on a full main/exports map: there's no runtime yet, the client exports arrives with the wrapper PR (so this avoids fighting that stacked branch), and an exports map here would prematurely restrict deep imports.
| "test": "c8 mocha", | ||
| "lint": "eslint .", | ||
| "lint:fix": "eslint --fix .", | ||
| "clean": "rm -rf node_modules build .counterfact", |
There was a problem hiding this comment.
Nit — clean script. rm -rf package-lock.json node_modules references a per-package lockfile that doesn't exist in this npm-workspaces monorepo (the lockfile lives at the repo root), and it skips the dirs this package actually generates.
There was a problem hiding this comment.
Fixed in e440235 — clean is now rm -rf node_modules build .counterfact: drops the phantom lockfile and clears the real generated artifacts (the build/ conversion intermediate and the Counterfact scratch dir).
| # Counterfact scratch dir (mock handlers materialized at runtime). | ||
| .counterfact/ | ||
|
|
||
| # The generated Python package is committed (model.py, aiseo.py, http_server.py, |
There was a problem hiding this comment.
Should fix — stale comment. This said "only models.py is committed," but the committed file is model.py (singular) and four modules are actually committed: model.py, aiseo.py, http_server.py, __init__.py.
There was a problem hiding this comment.
Fixed in e440235 — the comment now states that the generated Python package (model.py, aiseo.py, http_server.py, __init__.py) is committed and only the bytecode caches are ignored.
| "statements": 100, | ||
| "all": true, | ||
| "include": [ | ||
| "src/**/*.js" |
There was a problem hiding this comment.
Nit — coverage gate is currently vacuous. include: ["src/**/*.js"] matches no files in this foundation PR (only src/generated/types.ts exists, and it's excluded), so with all: true + check-coverage: 100 c8 reports 0/0 and the threshold passes trivially.
There was a problem hiding this comment.
Intentional — left as-is. This is forward-looking config: the gate starts enforcing 100% the moment the client's src/**/*.js lands in the wrapper PR. Confirmed npm test still exits 0 today (6 smoke tests pass), so no change needed here.
Carry `private: true` from the foundation PR (#1660) onto the wrapper so neither PR's merge attempts a publish. A new package name cannot use the monorepo's OIDC trusted-publishing path for its first publish (see scripts/setup-npm-trusted-publishers.sh) — that binding is a privileged adobe-bot / @spacecat-admins step. Keeping both PRs private decouples review/merge from that step; "go public" lands as its own explicit PR once the trust binding exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Hey @aliciadriani,
Verdict: Comment - clean foundation scaffold with only minor consistency notes.
Changes: new @adobe/spacecat-shared-project-engine-client package vendoring the Semrush Project Engine Swagger 2.0 spec and wiring a type-generation pipeline (swagger2openapi, openapi-typescript, datamodel-code-generator) with smoke tests (18 files).
Non-blocking (4): minor issues and suggestions
- nit:
.nycrc.jsonsets"branches": 100while the monorepo convention (documented in root CLAUDE.md) is 97% branches. Stricter is fine but creates inconsistency with sibling packages - consider aligning to 97 or documenting why this package opts for 100. -packages/spacecat-shared-project-engine-client/.nycrc.json:8 - nit:
"publishConfig": { "access": "public" }is inert while"private": trueand could mislead a future contributor who flips private off without the OIDC trust binding. Consider removing it until the deliberate first-publish commit. -packages/spacecat-shared-project-engine-client/package.json:41 - nit: root
CLAUDE.mdsays "containing 22 packages" which becomes inaccurate (23) once this merges. Consider updating or switching to "20+ packages". -CLAUDE.md(Architecture Overview) - suggestion: the spec assertion
expect(spec).to.include('swagger: "2.0"')is brittle to YAML quote-style variation (swagger: '2.0'or unquoted). Since the spec is vendored and manually refreshed this is low-risk, but parsing YAML and asserting on the value would be more robust. -packages/spacecat-shared-project-engine-client/test/foundation.test.js:18
Suggestions for follow-up
- The committed Python models (Pydantic v2) have no CI validation gate - unlike the TS types which the smoke test exercises. When the spec is refreshed, someone could regenerate TS but forget Pydantic, and nothing would signal the drift. Consider adding a minimal Python smoke test (e.g.
python3 -c "from serenity_project_engine.model import ...") or agenerate:checkscript that diffs committed output against a fresh run. - Consider adding a SHA-256 checksum sidecar for the vendored spec (
spec/projectengine_swagger_public.yaml.sha256) so future refreshes can be verified against what Semrush provides - supply-chain hygiene for when the spec becomes load-bearing.
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 39s | Cost: $3.52 | Commit: e4402353669fdaf678230cef6d51cdc08b6e0530
If this code review was useful, please react with 👍. Otherwise, react with 👎.
|
Publishing note: This package ( Going public requires a one-time prerequisite: the npm trusted-publisher binding for this Go-public sequence, when ready: configure the trusted-publisher binding for This is the gate on LLMO-5461 #4 (adopting the client in |
Two non-blocking findings from the MysticatBot review (verdict: Comment): - CLAUDE.md: bump the monorepo package count 22 -> 23 (this package is the 23rd workspace), in the prose and the structure tree. - foundation.test.js: assert the vendored Swagger version with a regex (`/^swagger:\s*['"]?2\.0['"]?/m`) instead of a literal substring, so a future spec refresh that reformats the quote style doesn't break the test on a harmless change. Verified the regex still matches the vendored spec. Lint clean; 6 foundation smoke tests pass. The two settled findings (nycrc branches:100, publishConfig under private:true) are addressed by reply, not code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review — foundation slice (reviewed tip
|
|
Correction — supersedes my earlier triage comment above. That triage was written against tip #3 — done. #4 — done, and keeping it is the right call (I withdraw the earlier "decline"). #1 ( #2 ( #5 / #6 (Pydantic CI gate, spec checksum sidecar) — agreed, follow-up tickets. Not #1660 blockers. Net: #3 and #4 are addressed and pushed; #1 is settled by reply (rationale confirmed); #2 is settled by the author's publishing note, with one mechanical [Edited for honesty about what this comment originally got wrong. An earlier version mis-framed #2 as "publishable up-stack … likely intentional … the first real publish should be explicitly confirmed deliberate," implying a possible go-public. That was wrong: the missing |
Four "should-fix" items from the human review of PR #1660 plus one nit, folded into a single commit to keep the stack ripple to one rebase point: - spec:convert: prepend `mkdir -p build`. build/ is gitignored, and swagger2openapi --outfile does not create the output dir, so the README's refresh flow (`npm run generate`) hit ENOENT on a fresh clone. Verified: with no build/ present, the fixed script exits 0 and writes build/openapi3.json. It only passed before because build/ already existed. - Add LICENSE.txt, CODE_OF_CONDUCT.md, CONTRIBUTING.md (copied from a sibling package). The package declares Apache-2.0 and intends to publish; every sibling carries these, and a LICENSE belongs in the tarball. - Declare c8, mocha, mocha-multi-reporters in devDependencies. The test script and mocha config invoke them; they previously resolved only via root hoisting. Versions pinned to match siblings. (chai's lockfile node entry was also missing and is now recorded.) - foundation.test.js: copyright 2025 -> 2026 (file created June 2026). - .gitattributes: drop the dead `build/** linguist-generated` entry (build/ is gitignored, so the attribute never applies). Lockfile change is scoped to this package's node only; pre-existing sibling version drift left untouched. spec:convert/generate:ts, 6 unit tests, and lint all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The fresh-clone Fixed in fb36bfa (folded into one commit to keep the stack ripple to a single rebase point):
Deferred to follow-ups (tracked):
Acknowledged, no change here:
On the cross-stack publish flag: confirmed and escalated separately — the mock stack tip is go-public-shaped ( Pre-existing lockfile/manifest drift on this branch (athena/drs/utils behind the tree) is noted for a separate look so |
There was a problem hiding this comment.
Hey @aliciadriani,
Verdict: Comment - clean foundation slice, all prior review findings addressed.
Changes: new @adobe/spacecat-shared-project-engine-client package vendoring the Semrush Project Engine Swagger 2.0 spec and wiring a type-generation pipeline (swagger2openapi, openapi-typescript, datamodel-code-generator) with smoke tests (22 files).
Non-blocking (1): minor issues and suggestions
- nit: CLAUDE.md Package Catalog table explicitly enumerates all API client packages but does not include
project-engine-clientafter this PR merges. The Monorepo Structure glob (spacecat-shared-*-client/) still covers it, so the table is merely incomplete rather than misleading. Consider adding it when the package goes public. -CLAUDE.md(Package Catalog section)
Previously flagged, now resolved
- CLAUDE.md package count updated 22 to 23
- Spec assertion now uses regex instead of brittle literal match
spec:convertENOENT on fresh clone fixed withmkdir -p build- LICENSE.txt, CODE_OF_CONDUCT.md, CONTRIBUTING.md added
- devDependencies (c8, mocha, chai, mocha-multi-reporters) declared
- Copyright year corrected to 2026
- Dead
.gitattributesentry for gitignoredbuild/removed
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 0m 50s | Cost: $4.92 | Commit: fb36bfa5028dd4c9d14f8041fba3fd629c876d22
If this code review was useful, please react with 👍. Otherwise, react with 👎.
Addresses MysticatBot review nit: the Package Catalog table enumerated all API client packages but omitted project-engine-client. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Hey @aliciadriani,
Verdict: Approve - no new concerns; all prior findings resolved.
Changes: new @adobe/spacecat-shared-project-engine-client package vendoring the Semrush Project Engine Swagger 2.0 spec and wiring a type-generation pipeline (swagger2openapi, openapi-typescript, datamodel-code-generator) with smoke tests (22 files).
Previously flagged, now resolved
- CLAUDE.md Package Catalog now includes
project-engine-client - CLAUDE.md package count updated 22 to 23
- Spec assertion uses regex instead of brittle literal match
spec:convertENOENT on fresh clone fixed withmkdir -p build- LICENSE.txt, CODE_OF_CONDUCT.md, CONTRIBUTING.md added
- devDependencies (c8, mocha, chai, mocha-multi-reporters) declared
- Copyright year corrected to 2026
- Dead
.gitattributesentry for gitignoredbuild/removed
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 0m 28s | Cost: $2.23 | Commit: b6a689fa4bdf5a108bec4cd60d07f9690cacb145
If this code review was useful, please react with 👍. Otherwise, react with 👎.
|
🎉 This PR is included in version @adobe/spacecat-shared-project-engine-client-v1.0.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
What
Foundation slice for the new
@adobe/spacecat-shared-project-engine-clientpackage — the typed integration with the Semrush Project Engine API (/enterprise/projects/api).Part of LLMO-5459 (epic) / LLMO-5461 (client + generated types). This PR is foundation only: vendor the spec and wire the conversion-and-type-generation pipeline. The client wrapper, IMS handler move, and stateful mock land in follow-up PRs.
Scope of this PR
spec/projectengine_swagger_public.yaml(Swagger 2.0,basePath: /enterprise/projects/api). Manual-refresh only; Semrush provides the file, no endpoint access. Not patched/edited.spec:convert— swagger2openapi--patchv2→3.x →build/openapi3.json(intermediate, gitignored). Exists only to feed the type generators.generate:ts— openapi-typescript →src/generated/types.tsgenerate:pydantic— datamodel-code-generator →python/serenity_project_engine/(Pydantic v2)mock— Counterfact reads the raw v2 spec directly (:4010), never the converted artifact.spacecat-shared: JS + ESM, JSDoc-typed source, mocha + chai + c8,@adobe/eslint-config-helix. The scaffold's TS surface is ported to JS+JSDoc;types.tsis a type artifact only.test/foundation.test.jsasserts the vendored spec invariants (Swagger 2.0, basePath,Auth-Data-Jwtheader) and that generatedtypes.tscarries the expectedpathsinterface + known v1/v2 routes.Commit structure
feat(...)— vendored spec + generation pipeline + config + smoke testchore(...) [skip ci]— generated TS + Pydantic types, committed separately and markedlinguist-generated(collapsed in review, excluded from diff counts)Validation
npm run generateruns end-to-end (build/openapi3.json → types.ts → python package)npm test— 6 smoke tests passnpm run lint— clean🤖 Generated with Claude Code