Releases: gronke/web_modules
Release list
v0.7.0
The crate gains a tarball dependency source (below); the rest of this release carries the release pipeline and the maintainer documentation.
Fixed
vendorwarns about a package it vendored that nothing in the import map points at and, for one that publishes only TypeScript, namesweb_modules.sourceDependenciesas the remedy. An asset-only package legitimately maps nothing, so the remedy is offered as a conditional. Silence here is the worst case: the tree is on disk, the exit status is zero, and the break surfaces later in a browser — or not at all, while a stale inline import map still resolves. A git dependency is the usual cause, since a whole-repo archive derives no entry until it is compiled. (buildvendors throughbuild::build, which does not return the map, so it is not covered yet.)vendorreports how many packages and entries it wrote, so a run that quietly did less than expected is visible in the one line it prints.- A source dependency's compiled entry is checked against its own manifest: when the layout its
tsconfig.jsondescribes is not the one it was published with, vendoring says so instead of leavingauto_entriesto drop the package from the import map without a word. - A program source outside an explicit
rootDiris refused, astscrefuses it (TS6059). It was skipped, so an importer was emitted whose import had been compiled to nowhere and then deleted. rewriteRelativeImportExtensionsis honoured, so a package whose sources name./util.tsemits./util.jsbeside the file it names. An emitted specifier that still carries a TypeScript extension is refused, since the source it names does not survive vendoring.- A relative import resolves to the source of its own module format:
./foo.mjsis written byfoo.mts, and afoo.tssibling is no longer compiled in its place. filesandincludename a program's root files, so a source one of them imports is compiled too andexcludeno longer removes it. Only the selected files were compiled, and the cleanup then deleted the rest — a root importing a sibling emitted an import that resolved to nothing.- A missing
rootDiris inferred from the program's own input files, astscdoes, rather than from the text before a glob's first wildcard. A package whose sources all sit undersrc/deepemits from there, where its manifest points. .ctsis CommonJS source and is refused rather than renamed to.cjs, as is a config declaring CommonJS output throughmoduleor an ES 3 / ES 5 target. The transform strips types; it does not rewrite module code..d.mtsand.d.ctsare declarations, not sources. They passed as ordinary.mts/.ctsfiles and could be emitted as.d.mjs/.d.cjs.- An absolute
rootDiroroutDiris refused rather than read as package-relative: it was safe from escape but silently compiled to a layout the dependency did not ask for. - A source dependency's
rootDirandoutDirare refused unless they stay inside the package, and the source root is deleted only when it is a strict descendant that does not hold the output. The config comes from a downloaded archive, sorootDir: ".."named the directory holding the package — which was then walked, written beside, and removed. files,includeandexcludeselect which files are compiled, with the directoriestscexcludes regardless and the output directory subtracted. Everything under the source root was compiled, so an excluded dev or test source was emitted, and one that did not compile failed the vendoring.- A
tsconfig.jsonnaming a JSX mode, import source or factory is refused:.tsxcompiles, but through the transform's own JSX handling rather than the one the config asks for. - A source dependency is compiled with its own emit semantics rather than this project's:
experimentalDecoratorsanduseDefineForClassFieldsare read from itstsconfig.json, and without the latter the target decides, astscdoes. The zero-config compile is the Lit preset, which gave a dependency legacy decorators and assignment-style class fields it never declared. .mtscompiles to.mjsand.ctsto.cjs, and.ctsreaches the compiler at all. Every compiled file was written as.js, so a package whoseexportsnameslib/index.mjshad no such file and lost its import-map entry.- A
tsconfig.jsonincludemay name a file rather than a glob.include: ["src/index.ts"]was read as a directory namedsrc/index.ts; entries now contribute the directory they name, and the root is the one they share. - A compiled destination's cache key carries a compile fingerprint, so a pinned commit recompiles when the compiler changes instead of keeping output from an older release.
- A source dependency is no longer also vended as a plain git dependency, which fetched one repository into two directories named differently — after the repository and after the dependency key.
- A source dependency keeps its licence and notice files too, alongside the sources it compiles.
- A vendored package keeps its
LICENSE,NOTICE,COPYINGandAUTHORSfiles, which the asset filter dropped. Serving a vendor tree is redistribution, and MIT and Apache-2.0 both require the notice to travel with the code.
Changed
- CI restores a warm
target/again: the cache action's target entry is restore-only by design and the paired save was never called, so every run — pull request and main alike — compiled the whole workspace cold across each feature set it checks. Saving on main makes pull requests consumers of it. - CI no longer runs
apt-geton either happy path: the build'szstdinstall had nothing to install ("already the newest version, 0 newly installed"), and Playwright's--with-depsnow runs only when the browser cache misses. - Every CI job carries a
timeout-minutesbackstop. A stalled apt mirror had hung the build for over 40 minutes and an e2e job for 25, both otherwise bounded only by the six-hour default. - The CLI section states why
--features cliis required and that the action needs no install. - crates.io publication is gated on a human signature covering the release commit — the signed
v<version>tag satisfies it. An unsigned release rehearses the packaging instead of uploading, and a signedv<version>-sigcompanion pushed later completes the publication, retroactively. - Before the moving
v0advances onto a release, the pipeline downloads that release's own binary through the action's installer mode — the path every@v0consumer takes — and checks it reports the right version. A release that cannot serve its binary leavesv0on the last one that could, and the crate unpublished with it. - One job now establishes the draft release before the binary matrix fans out. Six runners each creating it raced, and GitHub lets drafts share a tag name, so the losers became rival empty drafts — which is how v0.6.0 went live serving no binaries until its assets were copied across by hand.
- Installer mode is no longer exercised on pull requests: it downloaded the current release, so a pull request could not break it and an unrelated release fault failed every pull request. The release pipeline proves it instead, before
v0moves.
Added
tsconfig::TsConfigreads a package'stsconfig.json— the JSONC the format really is, viajsonc-parser— into a typed config: the layout to reproduce and the emit-relevant options. Replaces a hand-rolled comment stripper that ended a string at an escaped quote.walk::files_withinwalks a tree that came from outside the project without following symbolic links out of it, andwalk::containscompares what a path reaches rather than what it reads. Used where an extracted archive is read.ClassFieldssets class-field semantics independently ofDecorators, so standard decorators can pair with assignment semantics — the combinationtscuses below ES 2022.- A git dependency on a branch or tag is keyed on the archive's contents rather than the reference name, so moving the branch re-vendors instead of silently keeping the old tree; a commit id is keyed on itself and still costs no network once vendored.
- Source-built dependencies: a package named under
web_modules.sourceDependenciesis fetched from its git reference and compiled into the layout its owntsconfig.jsondeclares, so what lands inweb_modules/is browser-ready JavaScript with entries derived from its own manifest — what a package publishing only TypeScript needs, and reachable from the CLI, not just a Rust driver. examples/esptool-git: esptool-js consumed from its git reference and compiled by vendoring, with a Web Serial page that reads a connected ESP32's chip info over a bareesptool-jsimport.- A CommonJS-only package entry gets a generated ESM wrapper, with the bare import-map specifier pointing at that — a dependency shipping no ESM entry at all is otherwise unimportable in a browser.
PackageSpec::tarball(name, url)and apackage.json.tgzdependency form: vendor a pre-packednpm packtarball from an absolute https URL — e.g. a GitHub Release asset — extracted and import-mapped like an npm package, so a component library can be consumed straight from a Release without a registry. A…/releases/download/….tgzURL is recognised ahead of thegithub:shorthand.- MAINTENANCE.md: what a maintainer of this repository, or of a fork, has to have, configure once, and do on each release, with the failures worth recognising and their fixes.
scripts/setup-release.shperforms that setup idempotently — thecrates-ioenvironment restricted tov*tags with an optional reviewer, the tag rulesets, theci:taurilabel, and the crates.io trusted publisher through the registry's API. Immutable releases has no REST surface and is reported rather than attempted.
v0.6.0
v0.6.0
Added:
- The action's build input (default "true"): with build: "false" the action installs the SHA256-verified binary onto PATH and skips the build, for jobs whose own scripts drive web-modules (a repo build script, vendor, npm audit)
- Releases run through gronke/rust-ci's release flow: cut.yml cuts a release/v branch from CHANGELOG.md and Cargo.toml, the pipeline drafts a reviewable pre-release with an unsigned rc marker, and the human-signed v tag is sealed against the reviewed tree before the draft flips live with the binaries attached
Changed:
- docs(security): SECURITY.md describes the current posture — the SCSS import sandbox (the stale "processors are not sandboxed" caveat is gone), CLI config containment, bundle containment, the exact-pinned decorator runtime, and the trust anchors that remain (lockfile integrity is self-referential, vendored packages are not integrity-pinned, npm:// resolution ascends ancestors, Mount::from_dir is a config-trust boundary, no Host validation on the dev server)
- npm-utils 0.6.2 — the https scheme guard covers every request in a redirect chain, and the cache wipe unlinks symlinks instead of following them
Fixed:
-
fix(typescript): an _-prefixed .ts/.tsx/.mts source compiles like any other module — the underscore-partial convention belongs to SCSS, where _x.scss is an import-only fragment; ES modules have no such concept, and skipping _Base.ts stranded every import './_Base.js' in the emitted tree (surfacing only at bundle time, as an unresolved import). .d.ts declarations remain no-emit
-
fix(dev): live .tera renders receive the import map baked into the embedded fallback (its importmap.json, the contract artifact build emits) instead of always an empty one.
In the Frontend::embedded(&DIST).source("web") composition, an edited page previously rendered {"imports":{}} while the fallback kept serving the vendored modules, so bare specifiers (import { LitElement } from 'lit') failed to resolve in live mode.
Without an embedded fallback the map stays empty as before; an unparseable baked map warns and falls back to empty -
fix(scss): a sandbox-refused
@use/@importno longer reads like a missing file — the compile error appends a note: naming every existing path a probe was refused on and points at the missing load path (grass resolves imports through is_file probes, so the refusal in read was unreachable and the failure surfaced only as "Can't find stylesheet to import")
Security:
- security(cli): path fields in a package.json web_modules block (roots, out, template, scss.loadPaths) are confined to the project directory — previously an untrusted repository could serve arbitrary directories via web-modules dev, read any file into the output via template, and plant a new tree at an arbitrary location via out. Every entry must now be purely relative (no root, prefix, or .. component), and an existing path must canonically resolve inside the project, so a symlink in the tree cannot redirect it outside. CLI flags and environment variables are operator-controlled and unaffected
- security(bundle): module resolution is contained to the bundle root (bundle_split) / cwd (bundle) — a ../.. import chain or a symlinked package in node_modules that escapes the tree now fails the build instead of folding arbitrary local files into the published bundle. A workspace node_modules/ link pointing outside the project must be brought inside (or the tree bundled from a common root) — the build names the module it refused
- security(icons): source PNGs decode with strict dimension limits (4096×4096) on top of the image crate's 512 MiB allocation cap — a crafted icon source declaring enormous dimensions is refused at the header instead of exhausting memory
- security(build): paths and messages emitted into cargo: directives are kept free of control characters — a walked filename containing a line break could previously inject arbitrary directives (cargo:rustc-link-lib=…) into a build script's output. Such paths are skipped with a plain stderr note; warnings take the stderr path
- security(dev): a compile failure answers 500 with a generic body — the detail, which can embed absolute local paths (the SCSS sandbox's refusal notes name them), goes to the developer's console only, so a client that can reach the dev server (e.g. a DNS-rebinding page) learns nothing about the local layout
- security(build): the oxc transform runtime is vendored at an exact pinned version (0.138.0, tracking the oxc toolchain) instead of a floating ^0.137 range that resolved the newest published package at build time — a decorator in a source file no longer picks up whatever the registry newest-serves
v0.5.1
web_modules v0.5.1
- feat(build,serve):
npm://symlink assets — source node_modules files without committing copies - fix(serve): filesystem reads and on-the-fly compiles run on tokio's blocking pool
- fix(dev): a response that fails to build is a 500, not a panic
- fix(build,dev): reject-list drops are warned on stderr
- change: standalone tree helpers skip symlinks entirely
- deps: oxc 0.138, quick-xml 0.41, npm-utils 0.6.1
- ci: a cargo-audit (RustSec) job
v0.5.0
web_modules v0.5.0
- feat: selectable symlink modes for build, dev, and serving (
--symlinks) - feat(build): duplicate output detection,
--skip-duplicatesprecedence - feat(build): staged output, replacing
--outatomically - feat(build): emit-time module graph validates bare imports
- fix(build): the reject list covers every emitted target
- remove:
minify_directory - deps: npm-utils 0.6
v0.4.0
web_modules v0.4.0
- feat(build): make
buildthe static counterpart ofdev - feat: a zero-config
web_modulesblock in package.json drives dev/build; build auto-vendors its dependencies - feat: fluent Build and Dev library builders, behind a default-on
builderfeature - feat: PackageSpec::parse, and Decorators at the crate root
- refactor: processor-agnostic build pipeline - build()/BuildOptions/Processors need no
typescript; DevConfig aliases Processors - remove: the
compilecommand, folded intobuild - deps: bump npm-utils to 0.5.3 - native TLS roots, stricter sha512 integrity, hardened extraction
- deps: drop grass's clap CLI from the default build
v0.3.0
web_modules v0.3.0
- feat(action): Re-usable GitHub Action
- fix(vendor): emit cargo:rerun-if-changed for vendored destinations
- chore(deps): bump npm-utils to 0.5.2