Conversation
@yao-pkg/pkg drops a pkg.scripts entry that matches no file silently, so the module is missing from the packaged binary and the service crashes at runtime with MODULE_NOT_FOUND (the class of failure in DocumentServer#267). Add tools/check-pkg-scripts.js (Node built-ins only), an npm script, a jest fixture test, and a CI gate in e2e.yml that runs after install and before pkg. Complements the module-specific packaged smoke test in #46 with a generic check over every pkg.scripts entry in all four components. Signed-off-by: j-base64 <jcentenero@arsys.es> Assisted-by: ClaudeCode:claude-opus-4-8
…after self-review) Address review findings on the initial guard: - Narrow glob detection so a literal path containing a bare @, + or ! (e.g. a scoped-package path like node_modules/@scope/pkg/index.js) is checked as a file instead of being wrongly refused as a glob. Real globs and extglobs are still caught via their glob syntax. - Discover components that declare pkg.scripts instead of hardcoding the list, so a newly added component cannot silently escape the guard. - Run the guard in the production image build (server.bake.Dockerfile) before pkg, not only in the e2e CI mirror. - Add tests for the scoped-literal, non-string, no-scripts and discovery cases. Signed-off-by: j-base64 <jcentenero@arsys.es> Assisted-by: ClaudeCode:claude-opus-4-8
…f-review) Second-review fix. discoverComponents scanned only depth 1-2, so a component nested deeper would be silently skipped, the same silent-omission class the discovery was meant to remove. Recurse the full tree instead, pruning node_modules/.git/tests and not following symlinks (a real tree is acyclic, so the walk cannot loop). Add a nested fixture + test to lock it in, and note that manifest JSON validity is out of scope (npm install, which precedes the guard, catches a malformed package.json first). Signed-off-by: j-base64 <jcentenero@arsys.es> Assisted-by: ClaudeCode:claude-opus-4-8
|
I checked it against the real toolchain rather than only reading it: I built binaries with Here is what I found, most important first. 1. Merge timing: this breaks the DocumentServer builds, not only e2eThe guard runs in So please don't merge this on its own while it is red. Either merge it with or right after #46, or drop the three stale 2. A string-form
|
|
Thanks for the review! On "1. Merge timing": this is the point I raised in the PR description under "Timing to merge". I don't have a single fixed answer and saw it as an open question: whether this PR should depend on the timing of the related PRs like #44 or #46. a. Either we wait and land this with/after the one that adds the currently missing file, I lean toward b via a small dedicated PR, as it could streamline getting this landed. Happy to discuss which way we prefer. I will go through your other points and address them asap. Thanks again! |
TL;DR: A
pkg.scriptsentry that matches no file is silently dropped from the packaged binary,and only crashes with
MODULE_NOT_FOUNDat runtime when a specific code path is exercised. Withoutexercising that path, it's easy to assume a feature is in the build when it's not (it was the case for DocumentServer#267).
This PR adds a small build guard that fails when any component's
pkg.scriptsentry resolves to nofile, and runs it before the
pkgstep in both the production image build and the e2e CI job.🔥Ready for review; it stays a draft only because e2e is correctly red until #46 adds the editorDataRedis module or we decide to remove the stale entries (+details below)
Details
A component's
package.jsonpkg.scriptsblock reads like the list of modules that ship in thebuild, so an entry there implies the module is present. It may not be:
@yao-pkg/pkgbundles thefiles listed under
pkg.scripts, and if an entry matches no file it is dropped silently (nowarning, exit 0). The module is simply absent from the binary, and the only symptom is a
MODULE_NOT_FOUNDcrash the first time that code path runs, never a build failure. This is thesame class of silent-packaging gap that issues like DocumentServer#267 surfaced, and the same class of gap the Redis work in #44 and #46
runs into.
Each of the four components (
DocService,FileConverter,Metrics,AdminPanel/server)declares
pkg.scriptsentries, and a typo or a deleted/renamed source file would reintroduce thesame silent gap. This guard turns that class of silent omission into a loud build failure. It
complements a module-specific packaged smoke test like the one in #46: that proves one specific module is
bundled; this generic check protects every
pkg.scriptsentry in every component, at build time.How it resolves globs (faithful to pkg)
@yao-pkg/pkgresolves eachpkg.scriptsentry aspath.join(componentDir, entry)and thenglobs it (via
tinyglobby,{absolute:true, dot:true}), bundling a result only if it is afile. Every current
pkg.scriptsentry is a literal path, and for a literal pattern thatresolution is equivalent to a plain
fs.statSync(resolved).isFile()check. The guard thereforeuses that check and needs no new dependency. It also:
!entry as a pkg exclusion (not a file requirement); and@,+or!inside a path as literal (so a scoped-package path such asnode_modules/@scope/pkg/index.jsis checked, not mistaken for a pattern), while refusing anentry that contains real glob syntax rather than guessing at it (none exist today).
It must run after
npm install, since some entries point intonode_modules(e.g.axios,statsd) that only exist post-install, exactly aspkgrequires. In both the production imagebuild and the e2e CI job it is wired after the component installs and before the
pkgstep.No dependency choice
I considered using pkg's own matcher (
tinyglobby) so the guard could resolve globs exactly aspkg does. I did not, for two reasons: every current
pkg.scriptsentry is a literal path, forwhich the built-in
fscheck is already identical to what pkg does; and a real glob is refusedrather than resolved, so no glob engine is needed. Staying dependency-free also means the guard
runs anywhere
nodeis available, including build and CI steps that do not install the repo'sroot dev-dependencies. If a glob is ever added to
pkg.scripts,tinyglobby(pkg's own matcher)is the documented upgrade path.
Discover components choice (instead of a static list)
The guard finds what to check by scanning for
package.jsonfiles that declare apkg.scriptsblock, rather than hardcoding the list of components. A fixed list is one more thing to keep in
sync by hand, and its failure mode is quiet: add a component (or introduce
pkg.scriptsin anexisting one) and forget to update the list, and that component's entries silently escape the
guard, the very silent-omission this change exists to prevent. Discovery keeps coverage
automatic.
Possible objections
We could reconsider, going back to a static component list to address specific parts only, or even dropping this guard
altogether, if it turns out other developers deliberately rely on pkg's permissiveness (a missing
file dropping silently) to keep a module optional. But that is a very fragile mechanism: it is exactly
what produced the issues this PR addresses, which is why we want to catch the problem at build
time.
Changes
tools/check-pkg-scripts.js: the guard (Node built-ins only, no new dependency).package.json:npm run check:pkg-scripts..docker/server.bake.Dockerfile: run the guard before thepkgbuild (guards the realproduction packaging step).
.github/workflows/e2e.yml: run the guard after install, beforepkg(fast PR feedback).tests/unit/checkPkgScripts.tests.js+tests/fixtures/pkgScripts/*: regression tests(valid, zero-match, scoped-literal, glob-refusal,
!-exclusion, non-string, no-scripts,missing package.json, and component discovery).
Try it
Timing to merge
The guard only passes once the files that
pkg.scriptspoints at actually exist. Onmaintodaya few entries reference a module that has not been added yet (for example the
editorDataRedismodule that #46 introduces), so the guard will correctly fail and CI stays red until that module
lands. Two options for merge timing:
editorDataRedis);the entries already exist, so the guard goes green once the file is there.
module must re-add its
pkg.scriptsentry (otherwisepkgwould drop it again).Hope this helps close this class of silent-omission gap..
Review and feedback appreciated,
thanks!