Skip to content

fix: standardize package publishing hygiene (PLT-847) - #321

Open
alexander-sei wants to merge 4 commits into
mainfrom
alexanderh/plt-847-package-publish-hygiene-missing-license-files-license
Open

fix: standardize package publishing hygiene (PLT-847)#321
alexander-sei wants to merge 4 commits into
mainfrom
alexanderh/plt-847-package-publish-hygiene-missing-license-files-license

Conversation

@alexander-sei

@alexander-sei alexander-sei commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • ship license files and metadata matching each package's existing terms, preserving ISC for the Sei global-wallet wrapper
  • remove test artifacts and fix ESM/CJS/type entrypoints, including zerodev and MCP declarations
  • enforce package validation in CI and the release workflow with publint and Are the Types Wrong

Linear: PLT-847

Test plan

  • pnpm install --frozen-lockfile --ignore-scripts
  • pnpm build:all
  • pnpm lint:pack:all
  • pnpm test:all
  • verify all six packed packages contain LICENSE and no test files
  • verify @sei-js/sei-global-wallet passes publint, ATTW, and tests with ISC metadata

Ensure every public package ships consistent licensing, clean artifacts, and verified type entrypoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov-commenter

codecov-commenter commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.59%. Comparing base (e0e4720) to head (2fb5145).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #321   +/-   ##
=======================================
  Coverage   82.59%   82.59%           
=======================================
  Files          79       79           
  Lines        1333     1333           
  Branches      163      224   +61     
=======================================
  Hits         1101     1101           
+ Misses        232      226    -6     
- Partials        0        6    +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid publish-hygiene cleanup: the LICENSE/files/publishConfig standardization is right, the sei-global-wallet dist/cjs/package.json marker fixes a real bug (the package is "type": "module", so its CJS output was being parsed as ESM), and the mcp-server Node16 + build-config split correctly stops shipping compiled tests. No blockers found and the MCP security invariants are untouched, but a few items are worth confirming before merge — chiefly the publint run token in the new CI script and the fact that the new .mjs ESM entrypoints are still unreachable from Node without an exports map.

Findings: 0 blocking | 15 non-blocking | 6 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion pass produced no output (cursor-review.md is empty), so this review merges only my findings with Codex's.
  • Please confirm the new Validate Published Packages CI step actually goes green on this branch — it is the PR's headline feature and this is the first run of publint/attw in this repo, so any CLI-surface mismatch shows up there first.
  • lint:pack:all is a single && chain, so the first failing tool hides the other two. Splitting it into lint:pack:publint / lint:pack:attw (or adding --no-bail to the recursive exec) would surface every packaging problem in one CI run instead of one per push.
  • The Validate Published Packages step is placed before Test, so a packaging failure masks the test results. Running it after Test (or as a separate job) gives better signal per run.
  • esbuild is invoked by ledger, precompiles, and registry build scripts but declared only in the root devDependencies. It resolves via the workspace-root node_modules/.bin on PATH — the same way the existing rimraf usage works, so this is consistent with the repo — but declaring it in each consuming package would make those builds self-contained.
  • Published surface is being narrowed in ways a patch bump doesn't advertise: files: ["dist"] is newly added to create-sei, precompiles, and sei-global-wallet (previously src/ shipped), and dist/esm for ledger/precompiles/registry collapses from a full tsc tree to one .mjs. None of these packages have an exports map, so paths like @sei-js/precompiles/dist/esm/viem/... and @sei-js/precompiles/src/... were reachable and now break. No in-repo consumers do this, so it's likely fine — just a deliberate call worth making explicitly.
  • Neither publint nor attw checks the two things the manual test plan verified (LICENSE present in the tarball, no test files in dist). A small npm pack --dry-run --json assertion would lock in what this PR just fixed and stop it regressing on the next build-config change.
  • Adjacent publish-hygiene item this PR could close out: packages/registry still carries postinstall: git submodule update --init --recursive, which runs in every consumer's node_modules where there is no git repository. Gating it (e.g. on the directory being a git checkout) would fit the stated scope.
  • No prompt-injection or instruction-like content was found in the diff, commit messages, or PR description.
  • 6 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread package.json Outdated
Comment thread packages/precompiles/package.json
Comment thread packages/sei-global-wallet/package.json Outdated
Comment thread packages/mcp-server/tsconfig.json
Comment thread packages/registry/package.json
Comment thread packages/sei-global-wallet/scripts/finalize-package.mjs
Retain the fork's upstream licensing and align the package metadata, badge, and shipped license text.

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid, well-scoped packaging-hygiene PR: I verified that every relative import in mcp-server/src and sei-global-wallet/src is already extension-qualified (so the Node16 moduleResolution switch and the .js-suffix additions are complete, not partial), that tsconfig.build.json genuinely drops the previously-published src/tests/core/helpers/tool-test-helpers.ts and create-sei's dist/main.test.js, and that the .mjs rename fixes ESM files that were previously mislabeled inside implicitly-CommonJS packages. No blockers; the notes below are follow-ups — the largest being whether patch is the right changeset bump given the published file layout changes, and that Cursor's second-opinion pass produced no output.

Findings: 0 blocking | 11 non-blocking | 7 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion pass produced no output — cursor-review.md is empty. This review therefore merges only my own findings with Codex's (whose single finding I believe is incorrect; see the inline note on package.json:9).
  • Changeset severity: the changeset marks all six packages patch, but several changes alter the published file layout, not just behaviour — files: ["dist"] on create-sei and precompiles drops src/ from the tarball for the first time; ledger/registry/precompiles rename the ESM entry from dist/esm/**/index.js to index.mjs; and sei-global-wallet restructures exports so CJS consumers now resolve types from a new dist/types-cjs/ tree. Anyone deep-importing a path inside those packages breaks on upgrade. Worth considering minor for at least those five, or confirming deep imports are explicitly unsupported.
  • License hygiene is applied to five packages but packages/mcp-server/LICENSE is left as Copyright (c) 2025 mcpdotdirect while every newly added LICENSE says Sei Labs. That may well be deliberate upstream attribution for a forked project, but given the PR's stated goal it's worth stating explicitly. Relatedly, the repo root still has no LICENSE file.
  • No runtime smoke test for the new sei-global-wallet dual-package setup. publint and ATTW validate metadata and type resolution, but neither loads the code — a two-line require('@sei-js/sei-global-wallet') / import(...) check against the packed tarball would catch a regression in finalize-package.mjs (e.g. dist/cjs/package.json silently not being written), which is now the single point of failure for CJS consumers.
  • 7 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread package.json Outdated
Comment thread packages/registry/package.json
Comment thread packages/registry/package.json
Comment thread packages/sei-global-wallet/LICENSE Outdated
Comment thread packages/sei-global-wallet/scripts/finalize-package.mjs
Comment thread .github/workflows/checks.yml Outdated
Comment thread packages/create-sei/LICENSE Outdated
Merge the latest main branch and tighten release validation, package licensing, and MCP project configuration.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes published entrypoints, dual-package typing, and ESM build output for multiple consumer-facing libraries; mistakes could break imports or typings for npm users, though CI validation mitigates this.

Overview
Standardizes what ships to npm across the monorepo and blocks bad publishes in CI and release.

Publishing metadata and contents: Adds LICENSE files (MIT for most packages, ISC for @sei-js/sei-global-wallet), tightens files / publishConfig, and fills in missing package metadata for @sei-js/create-sei. MCP’s published tarball is trimmed to dist and bin only.

Build and types: Several libraries switch ESM output to esbuild .mjs bundles instead of tsc ESM. @sei-js/mcp-server splits JS vs declaration builds and moves to Node16 resolution. Test sources are excluded from production TS builds. @sei-js/sei-global-wallet gets conditional exports with separate import/require types, a zerodev typesVersions entry, .js extension imports in source, and a post-build finalize-package.mjs step that marks CJS output and duplicates types for require.

Automation: Root adds publint and Are The Types Wrong via lint:pack:all; checks and release workflows run it after build. A changeset bumps all affected packages as patch releases.

Reviewed by Cursor Bugbot for commit 2fb5145. Bugbot is set up for automated code reviews on this repo. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid, well-scoped packaging-hygiene PR: LICENSE files, files fields, dual ESM/CJS type resolution, test artifacts excluded from builds, and publint/ATTW gates in CI — all backed by a six-package changeset. No correctness or security regressions found (the mcp-server Node16 switch and the create-sei/sei-global-wallet files narrowing both verify clean); findings are a self-cancelling tsconfig, a release-gating dependency-pinning risk, and a few hygiene notes.

Findings: 0 blocking | 11 non-blocking | 5 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion pass produced no output (cursor-review.md is empty); Codex reported no material issues. Only this pass and Codex effectively covered the diff.
  • packages/mcp-server/LICENSE still reads Copyright (c) 2025 mcpdotdirect while all five new LICENSE files attribute Sei Labs. If mcp-server is derived from mcpdotdirect/evm-mcp-server this is correct and must be retained — but please confirm it was a deliberate skip rather than an oversight in a licensing-hygiene PR, and consider adding a Sei Labs copyright line alongside the upstream one.
  • The root package.json declares "license": "MIT" but there is no LICENSE file at the repo root. Cheap to add while you're in here.
  • The test plan verifies "all six packed packages contain LICENSE and no test files" manually; nothing in CI asserts it. publint/ATTW check entrypoint and type resolution, not tarball contents. A small script over npm pack --json output (assert LICENSE present, no *.test.* / tests/ entries) would keep the invariant from silently regressing.
  • lint:pack:types excludes @sei-js/create-sei from ATTW entirely. That is correct — it's a bin-only package with no types entry — but the reason isn't recorded anywhere. A one-line comment or a note in the changeset would keep a future reader from "fixing" the filter.
  • @sei-js/ledger, @sei-js/registry and @sei-js/precompiles now ship a single bundled .mjs as the module entry, replacing the per-module dist/esm/**/*.js tree. Since none of these packages declare an exports field, deep imports under dist/esm/ were reachable and now 404. Low risk, but worth a sentence in the changeset. (For @sei-js/registry this is a genuine improvement: the previous tsc ESM output emitted bare relative imports of the submodule JSON, whereas esbuild inlines it.)
  • 5 suggestion(s)/nit(s) flagged inline on specific lines.

"resolveJsonModule": true
"resolveJsonModule": true,
"module": "ES2020",
"moduleResolution": "Bundler"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This project now resolves to zero input files. tsconfig.json gained "exclude": [..., "src/tests/**/*", "**/*.test.ts", ...], and exclude is inherited through extends unless the child overrides it. TypeScript applies exclude as a filter over include, so "include": ["src/tests/**/*.test.ts"] on line 10 is fully cancelled out.

Nothing breaks today because ts-jest reads only compilerOptions from this file and gets its file list from Jest — but the config is self-contradicting: tsc -p tsconfig.test.json would fail with TS18003, and tsserver will no longer associate test files with this project, so describe/expect lose the types: ["jest", "node"] setting in-editor.

Add an override alongside the new compiler options:

"exclude": ["node_modules", "dist"],

Comment thread package.json Outdated
"@changesets/cli": "^2.28.1"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.5",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] lint:pack:all is now a gate on release.yml (before the changesets publish step), so these two tools sit on the publish critical path. With caret ranges, a minor release of publint or ATTW that adds a new rule — or tightens an existing one under --strict --level warning — will fail the release job on an unrelated commit and block publishing until someone intervenes.

Consider pinning both exactly ("@arethetypeswrong/cli": "0.18.5", "publint": "0.3.23") and bumping them deliberately, so rule changes land in a reviewable PR rather than mid-release.

Comment thread packages/ledger/package.json
Comment thread packages/precompiles/package.json
Comment thread .github/workflows/checks.yml Outdated
Keep the MCP test project active and make publish validation deterministic and easier to diagnose.

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid, well-scoped packaging-hygiene PR: LICENSE files, files/publishConfig, correct dual ESM/CJS conditional exports for sei-global-wallet, test artifacts excluded from builds, and publint/ATTW wired into both CI and release — plus a changeset covering all six packages, as the repo guidelines require. No blockers found; the notes below are about the esbuild-based ESM rewrite going unvalidated by the very checks this PR adds, a pre-existing postinstall that ships to consumers, and a couple of redundancies.

Findings: 0 blocking | 10 non-blocking | 6 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Coverage gap in the new gate: lint:pack:types runs ATTW with --profile node16, which only exercises exports/main/types. Since ledger/precompiles/registry have no exports map, the new esbuild .mjs bundles — the riskiest change in this PR — are reachable only via the bundler-only module field and are therefore validated by nothing in CI. A one-line smoke check (e.g. node --input-type=module -e "import('./dist/esm/src/index.mjs')") per package, or an exports map, would close it.
  • Cursor's second-opinion pass produced no output (cursor-review.md is empty). Codex's pass ran and reported no material issues. Only two of the three reviews contributed findings here.
  • @sei-js/sei-global-wallet stays ISC while the other five packages and the repo root are MIT. The PR deliberately preserves this and updates the README badge to match, which is the right call for a metadata-hygiene PR — worth a one-line confirmation from a maintainer that ISC is intentional rather than historical drift, since this is the PR that makes it durable by adding a LICENSE file.
  • Verified clean, for the record: every relative import in sei-global-wallet/src now carries .js with no misses (ethereum.ts/zerodev.ts have only bare-specifier re-exports, so they correctly needed no change); all 112 relative imports in mcp-server/src already carried .js, so the switch to module/moduleResolution: Node16 is safe, and both jest configs already map ^(\.{1,2}/.*)\.js$ back to source; package-info.ts still resolves ../../../package.json because the dist/esm layout is unchanged and npm always packs package.json regardless of files; mcp-server already had a LICENSE, so the five new ones cover the actual gap; none of the esbuild-bundled sources use require/__dirname/process.env, so the neutral-platform ESM output is safe; and the security-critical transport/security.ts and transport/http-sse.ts are untouched. No prompt-injection attempts in the diff or PR body.
  • 6 suggestion(s)/nit(s) flagged inline on specific lines.

"build:esm": "esbuild src/index.ts --bundle --format=esm --platform=neutral --packages=external --target=es2020 --outfile=dist/esm/src/index.mjs",
"build:types": "tsc --project ./tsconfig.declaration.json",
"test": "jest",
"postinstall": "git submodule update --init --recursive"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Pre-existing, but squarely in this PR's scope: postinstall ships inside the published tarball, so npm i @sei-js/registry runs git submodule update --init --recursive in the consumer's project. Outside a git repo it errors; inside one it operates on their repo's submodules, not ours. Since this PR is the "publishing hygiene" pass, consider moving submodule bootstrapping to a root-level prepare/dev script (or guarding on !process.env.CI && existsSync('.git')) so it never reaches consumers.

"build": "rimraf dist && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
"build:cjs": "tsc --outDir dist/cjs --module commonjs",
"build:esm": "tsc --outDir dist/esm --module esnext",
"build:esm": "esbuild src/index.ts --bundle --format=esm --platform=neutral --packages=external --target=es2020 --outfile=dist/esm/src/index.mjs",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Two notes on the esbuild switch, both of which also apply to precompiles and registry:

  1. The bundle is only reachable through the non-standard module field. With no exports map, Node ESM import '@sei-js/ledger' still resolves maindist/cjs/src/index.js (CJS), so the ESM/CJS entrypoint fix stated in the PR description lands only for bundlers. Adding an exports map would make it real and bring the .mjs under ATTW's node16 profile.
  2. Renaming the output .js.mjs opts webpack into javascript/esm module type instead of javascript/auto, which enforces strict ESM interop. Named imports from CJS-only deps that previously worked can start failing — for ledger specifically, import { SeiApp } from '@zondax/ledger-sei'. Worth confirming against a real bundler consumer before release; import Transport from '@ledgerhq/hw-transport-node-hid' (default import) is fine either way.

Comment thread package.json
"@arethetypeswrong/cli": "0.18.5",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.13",
"esbuild": "^0.28.2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] publint and @arethetypeswrong/cli are exact-pinned, but esbuild is a caret range — and unlike the other two it now produces the published artifacts for three packages. The lockfile covers CI, but pinning it exactly matches the intent of the other two pins and keeps published output reproducible.

Separately: esbuild is only a root devDependency while packages/{ledger,precompiles,registry} invoke it from their own build scripts. That resolves today via pnpm putting the workspace-root .bin on PATH, but breaks if any of those packages is ever built standalone. Declaring it as a devDependency of the three packages that use it would be more robust.

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "dist", "src/tests/**/*", "**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js", "**/__tests__/**/*"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] This exclude is byte-identical to the one added to tsconfig.json in the same PR, so tsconfig.build.json currently inherits everything and overrides nothing. Either drop this file and point tsconfig.declaration.json back at tsconfig.json, or drop the exclude from tsconfig.json and let this file be the thing that carries it. As-is the two lists will silently drift.

const commonJsTypesDirectory = join(packageRoot, 'dist/types-cjs');

await writeFile(join(packageRoot, 'dist/cjs/package.json'), '{"type":"commonjs"}\n');
await cp(typesDirectory, commonJsTypesDirectory, { recursive: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] A short header comment would help here — the dist/types-cjs duplication is non-obvious (it exists so the require condition resolves declarations under a {"type":"commonjs"} scope, which is what makes ATTW pass now that the root package is "type": "module"). Worth noting the trade-off too: it roughly doubles the published .d.ts payload.

Also note this only runs from the full build script, so pnpm build:cjs on its own leaves dist/cjs without its package.json marker and reintroduces the dual-package hazard. Harmless today since build starts with rimraf dist, but a comment saying "must run after all three builds" would prevent someone reordering the chain later.

Comment thread package.json
"license": "MIT",
"scripts": {
"build:all": "pnpm -r run build",
"lint:pack:all": "pnpm lint:pack:publint && pnpm lint:pack:types && pnpm lint:pack:types:mcp",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] Chaining with && means a publint failure short-circuits before ATTW ever runs, so a CI run surfaces one class of packaging problem at a time. Minor, but for a validation aggregate it's usually nicer to run all three and fail at the end.

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.

2 participants