Releases: C9up/rosetta
Release list
v0.3.4
Build the engine specifier instead of writing it inline
The publish guard refused the package:
dist/RosettaProvider.js imports @c9up/inker at runtime
It is right to. This package declares zero runtime dependencies and
verify-package.mjs proves the claim rather than trusting the manifest:
an optional peer that reaches the emitted JavaScript is a runtime
dependency whatever the manifest calls it. A literal import() is
exactly that, and it also makes a bundler try to resolve inker at build
time for an application that never installed it.
Computed through a variable, as ream's quasar bridge already does for
the same reason, it stays invisible to static analysis and still runs
only behind the usingInker flag. Guard passes, 253 tests, branches
73.52%.
Cover the engine shapes the provider dispatches between
CI failed on coverage, not on a test: branches came to 72.92% against a
73% threshold -- one branch in 1326. The uncovered ones were mine, in
the template-plugin install added when the provider stopped resolving
the engine from the container.
They were untested because the engine module mock had a single fixed
shape, and the provider duck-types between two real ones: a plugin
registrar with use(), and a live engine that only publishes globals.
A fixed mock can only ever exercise one. The mock now reads its default
export through a getter so a test can choose, which covers both paths,
the engine it recognises neither way (boot must not fail over i18n
globals), and the case where no engine is installed at all.
Also covers an emitter that answers a promise -- asserted by watching
for the unhandledRejection it exists to prevent, rather than by
asserting that nothing happened.
Branches 73.52%. 253 tests pass.
Reach the template engine through its module, not the container
The i18n globals were pushed by resolving the inker container token during
boot. That token is bound in register() and only resolvable after
start(), which every provider's boot() precedes — so the push either
threw or forced the engine to be built before its own peers were wired. In
the kitchen-sink it failed the whole application boot.
Upstream's i18n provider does not do this: it checks a flag on the app, then
pushes into the engine package's module singleton, whose use() only
enqueues. There is no lifecycle to get wrong. This now does the same, with
inker as an optional peer so an app with no template engine is unaffected.
Release 0.3.4.
Keep the dev-dependency alignment, drop the workspace: protocol
The internal ranges had been rewritten to workspace:^. That resolves inside
this monorepo and nowhere else: every package CI checks out its own repository
alone and runs pnpm install, where the protocol has no workspace to point at
and fails with ERR_PNPM_WORKSPACE_PKG_NOT_FOUND before a single test runs. The
concrete ranges are back; the dev-dependency bumps that came with the same edit
are kept, and now match what the lockfile already resolved.
Read the mustache spelling too when filling an untranslated message
The message that reaches the fallback interpolator is the VALIDATOR's own
default template, and those are written in mustache: {{ field }}. The
interpolator knew only the single-brace form an application writes in its own
catalogue, so it matched the INSIDE of the pair and left the outer braces
standing. Every rule an app had not translated yet rendered to the end user as
The {name} field must have at least {4} characters.
Both spellings are read now, mustache first so it wins on a pair.
Name the validation contract rather than the project behind it
Comments and test labels called the messages-provider contract by the name of
the library rune's shape was taken from. Parity is shape, never product
identity — and rosetta implements the contract rune publishes, which is the
thing worth naming here.
Comment and test-label only; no identifier, no runtime string.
Name the CI workflow after the package
Every workflow already declared name: <pkg>-ci inside — twenty-eight of
twenty-nine — while the file was ci.yml almost everywhere and
<pkg>-napi-ci.yml in three places, where the -napi said nothing: half the
packages with a Rust engine did not carry it.
The file now matches the name it has always had, so one rule covers every
repository and the publish command no longer depends on remembering which
three were spelled differently.
GitHub keys run history by file path, so the runs recorded under the old name
stay reachable under it and this workflow starts a fresh history.
Changes since v0.3.3.
v0.3.2
Freeze the clock the relative-time test reads
formatRelativeTime(new Date(Date.now() + 3_600_000), 'auto') measured the
distance a second time, from inside the formatter. The microseconds between
building the date and measuring it made the distance 59.99 minutes, the floor
said 59, and the assertion wanted "in 1 hour". It passed on a fast machine and
failed under the load of a full quality run.
The rounding itself is left alone, and that is the point worth writing down.
Math.floor rounds a negative value away from zero, so ninety minutes reads
"in 1 hour" ahead and "2 hours ago" behind — the same distance answered two
ways depending on its direction. Upstream 3.0.1 does exactly this. The strings
are user-visible, so a migrated application whose timestamps started reading
differently would have no way of knowing why: the asymmetry is pinned by a
test rather than corrected.
Cover the skeleton parser, and make both unit spellings agree
pnpm quality:packages was red here: lines 83.05 against 84, statements 81.17
against 83, branches 69.28 against 73. The gap was one file — the ICU skeleton
parser held 248 of the ~395 uncovered branches, which is the largest thing in
the package and the least exercised.
It turns a compact string into Intl options, and a wrong mapping produces
output that is plausible and wrong. So each expectation is computed from Intl
independently: what is checked is the mapping, not the formatting.
Writing them found a real one. measure-unit/length-meter dropped the
dimension prefix and unit/length-kilometer did not, so the short spelling
reached Intl as an unknown unit and threw. Both now go through the same helper.
Two of my own expectations were wrong and are pinned as the distinctions they
are: ::percent marks the unit without scaling where the classic percent
style multiplies by 100, and dateStyle/full is an Intl option rather than a
skeleton, refused by name instead of read as symbols.
Every threshold passes: statements 84.57, branches 73.52, lines 86.46.
Changes since v0.3.1.
v0.3.1
Use real private fields, not the TypeScript keyword
private is erased at compile time: at runtime the field is public,
enumerable, and shows up in Object.keys and JSON.stringify. # is enforced by
the engine. The difference is not cosmetic, and one test proved it — photon's
renderer test reached into a private ssrModule through an
as unknown as { ssrModule } cast, which the keyword never prevented. It now
goes through useSsrModule(), a real seam, and the cast is gone.
Constructor parameter properties are expanded into a field plus an assignment,
since # cannot be declared in a parameter list.
Three private CONSTRUCTORS stay as they are, annotated: that form has no native
equivalent, and it is the one place the keyword expresses something # cannot.
Create the GitHub release from the publish workflow
A published version arrived with no notes: npm showed a number, GitHub showed
nothing, and the only way to learn what changed was to read a diff. The commit
messages already carry the reasoning, so the release is built from the commits
the tag contains rather than written twice.
Skips a pure version bump, leaves an existing release alone, and does nothing
when the run was not built from a tag. The job takes contents:write for this;
the workflow default stays read.
Changes since v0.3.0.
rosetta v0.3.0
rosetta 0.3.0
rosetta: name the template plugin after inker, and wire it — 0.3.0
The i18n plugin carried the upstream engine's name throughout: the module,
the exported function, its parameter type, and the published export path.
None of that is ours to borrow — the engine is inker.
./plugins/edge -> ./plugins/inker (BREAKING: published path)
edgePluginI18n -> inkerPluginI18n
EdgeLike -> TemplateEngineLike
Renaming the container token exposed a dead path. The provider resolved a
service named edge; nothing has ever registered one — the provider that
does exist binds inker. So resolveOptional always returned undefined and
the i18n globals never reached a template. The unit test registered the same
absent token, which is why it stayed green.
Resolving inker alone is not enough either: that token is bound to a
renderer, which publishes no globals of its own — the engine it wraps does.
The provider now takes whichever of the two accepts a plugin or publishes
globals, behind type guards rather than a cast.
Covered by a test using the shape the provider actually meets in production
(a renderer exposing the engine), alongside the existing direct-engine ones.
1dc9438
rosetta: narrowing du rounding increment par garde de type
Set.has() ne prouve rien sur le type de la valeur, donc
l'affectation à roundingIncrement perdait le littéral. rosetta
typecheckait seul mais CASSAIT le typecheck de ses consommateurs (erreur
vue depuis inker).
Liste déclarée as const + garde de type, sans cast menteur.
Tests: 191 passés.
150fe3b
Changes since v0.2.0.