Releases: ingram-technologies/nextkit
Releases · ingram-technologies/nextkit
Release list
@ingram-tech/nk-seo@0.6.0
Minor Changes
-
6b188c2: Fix the hreflang/canonical slice and close a URL origin escape:
absoluteUrlthrows when the input escapes the site origin. A request tohttps://site//evil.com/xputs//evil.com/xintoreq.nextUrl.pathnameand thus intox-pathname; the old pass-through resolved it tohttps://evil.com/xand emitted it as the canonical — an SEO-hijack primitive once cached. Backslash forms and scheme'd inputs (javascript:) are rejected the same way.- The canonical now self-references per Google's spec. A localized variant that canonicalizes to another URL makes Google discard the whole hreflang cluster. The prefix strategy auto-detects the current locale from the pathname; the query strategy accepts a new
currentLocaleoption (the server can't see the query string). - The prefix strategy no longer double-prefixes.
x-pathnamecarries the real/fr/abouton a localized route; alternates were computed as/en/fr/about+/fr/fr/about. An existing locale prefix is now stripped first.strategy: "prefix"withoutdefaultLocalethrows at call time instead of silently emitting URLs no locale serves. - The documented middleware snippet actually works now. It set
x-pathnameon the response;headers()reads request headers, so<HreflangLinks>threw on every request — and its error message repeated the same broken snippet. README, JSDoc, and the error message all show theNextResponse.next({ request: { headers } })form, note that copyingreq.headersfirst neutralizes client spoofing, and document the static-rendering tradeoff of the header path. noIndex: trueemitsfollow: true(noindex pages should still pass link equity); query URLs append with&when the path already carries a query; duplicate React keys can no longer silently drop alternate links; the README's peer-dependency claim covers/og(which needs bothnextandreactat runtime).
@ingram-tech/nk-marketing@0.3.0
Minor Changes
- Add
subscribeToWire— a zero-dependency client for the Wire newsletter service (wire.ingram.tech). Consumer sites forward a signup with one import (await subscribeToWire({ email, source })) using their server-sideWIRE_API_KEY, instead of hand-rolling the fetch in every repo.
@ingram-tech/nk-marketing@0.2.4
Patch Changes
-
51d7812: nk-i18n:
negotiateAcceptLanguagehonors q-values per RFC 9110: the highest quality wins instead of raw header order, and aq=0(explicit rejection) can no longer be selected.t()no longer throws at request time on a malformed catalog entry, a missing placeholder value, or an invalid locale tag — it degrades to the raw message and warns once per key. Previously one badfrentry 500'd every French page rendering it, invisible to base-locale testing.- The ICU formatter cache is bounded (an unvalidated user-controlled locale could grow it without limit), and
MissingKeysPolicyis documented as reserved/not-yet-consumed.
nk-email:
fromAddressvalidates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).buildListUnsubscribeHeadersrejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.DEFAULT_TIMEOUT_MSis exported from the package root (it was referenced by public JSDoc but unimportable).
nk-marketing:
subscribe()clears a global opt-out — an explicit re-subscribe is fresh consent. Previously a contact who globally unsubscribed and later signed up again got a "successful" subscription but was silently excluded from every broadcast forever, with no code path able to detect it.identify/subscribevalidate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.- A failing
releaseDeliverycan no longer abort the rest of a broadcast batch or mask the original send error insendLifecycle. - Inbox preview text is sliced by code points, so a cut can't land inside an emoji's surrogate pair.
-
Updated dependencies [51d7812]
- @ingram-tech/nk-email@0.3.1
@ingram-tech/nk-i18n@0.1.3
Patch Changes
-
51d7812: nk-i18n:
negotiateAcceptLanguagehonors q-values per RFC 9110: the highest quality wins instead of raw header order, and aq=0(explicit rejection) can no longer be selected.t()no longer throws at request time on a malformed catalog entry, a missing placeholder value, or an invalid locale tag — it degrades to the raw message and warns once per key. Previously one badfrentry 500'd every French page rendering it, invisible to base-locale testing.- The ICU formatter cache is bounded (an unvalidated user-controlled locale could grow it without limit), and
MissingKeysPolicyis documented as reserved/not-yet-consumed.
nk-email:
fromAddressvalidates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).buildListUnsubscribeHeadersrejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.DEFAULT_TIMEOUT_MSis exported from the package root (it was referenced by public JSDoc but unimportable).
nk-marketing:
subscribe()clears a global opt-out — an explicit re-subscribe is fresh consent. Previously a contact who globally unsubscribed and later signed up again got a "successful" subscription but was silently excluded from every broadcast forever, with no code path able to detect it.identify/subscribevalidate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.- A failing
releaseDeliverycan no longer abort the rest of a broadcast batch or mask the original send error insendLifecycle. - Inbox preview text is sliced by code points, so a cut can't land inside an emoji's surrogate pair.
@ingram-tech/nk-email@0.3.1
Patch Changes
-
51d7812: nk-i18n:
negotiateAcceptLanguagehonors q-values per RFC 9110: the highest quality wins instead of raw header order, and aq=0(explicit rejection) can no longer be selected.t()no longer throws at request time on a malformed catalog entry, a missing placeholder value, or an invalid locale tag — it degrades to the raw message and warns once per key. Previously one badfrentry 500'd every French page rendering it, invisible to base-locale testing.- The ICU formatter cache is bounded (an unvalidated user-controlled locale could grow it without limit), and
MissingKeysPolicyis documented as reserved/not-yet-consumed.
nk-email:
fromAddressvalidates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).buildListUnsubscribeHeadersrejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.DEFAULT_TIMEOUT_MSis exported from the package root (it was referenced by public JSDoc but unimportable).
nk-marketing:
subscribe()clears a global opt-out — an explicit re-subscribe is fresh consent. Previously a contact who globally unsubscribed and later signed up again got a "successful" subscription but was silently excluded from every broadcast forever, with no code path able to detect it.identify/subscribevalidate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.- A failing
releaseDeliverycan no longer abort the rest of a broadcast batch or mask the original send error insendLifecycle. - Inbox preview text is sliced by code points, so a cut can't land inside an emoji's surrogate pair.
@ingram-tech/nk-dev@0.3.0
Minor Changes
-
79bcb7d: Toolchain consolidation:
nkno longer formats SQL.nk format/nk checkrun oxfmt only; theprettier+prettier-plugin-sqldependencies are dropped. SQL in these repos is ~all generated (drizzle migrations,pg_dumpbaselines, pglite fixtures), so formatting it only churned generated files and crashed on psql directives (\restrict) for no gain.nk test— new passthrough forvitest run(extra args forwarded), completing the set alongsidelint/format/check/type-check/build.nk doctor [--fix]— reports a site's drift from the canonical nk-dev toolchain (scripts not pointing atnk, superseded deps,.oxlintrc.json/tsconfig.jsonextends not pointing at nk-dev, a missing CLAUDE.md guide import, staleknip.jsonignores, a dead.prettierignore) and, with--fix, applies the mechanical corrections. Exits non-zero on model-breaking findings so it can gate CI.nk checkwarns on tooling drift — a non-fatal notice when a site re-declares a package nk-dev supersedes (oxfmt,oxlint,prettier*,@ingram-tech/{oxlint,typescript}-config,@ingram-tech/nk-cli,@ingram-tech/git-hooks), pointing atnk doctor --fix.
Patch Changes
- c8df237: Bump the bundled oxc toolchain: oxfmt
^0.56→^0.58, oxlint^1.71→^1.73. Sites on the nk-dev toolchain pick these up on their next install, so the whole fleet's formatter/linter version is managed here in one place rather than pinned per repo.
@ingram-tech/nk-dev@0.2.5
Patch Changes
-
d430473: Pre-commit hook and exit-code correctness:
format-stagedno longer commits unstaged hunks.oxfmt --writerewrites the working tree and the re-git addswept everything into the commit — including hunks deliberately left out withgit add -p. Partially staged files are now skipped with a warning.- Non-ASCII filenames are formatted again.
git diff --name-onlyoctal-escapes them ("\303\251 test.ts"), which matched no real path, so such files were silently never formatted or re-staged; same fix innk format's SQL file listing. Both now use-z/NUL splitting. - Signal-killed tools fail the gate.
run()/nk devtreated anullexit status (OOM-kill, SIGSEGV) as success, letting a crashed linter passnk check. nk checkreads the SQL result fromformatSql's return value instead of theprocess.exitCodeglobal (which misattributed any earlier failure to SQL); SQL formatting defaults carry the housetabWidth: 4/printWidth: 88(Prettier's own 80/2 applied before); deleted-but-tracked.sqlfiles no longer crashnk format; the hook usesexistsSyncinstead of spawning the Unixtestbinary per file; the unusedcapture()helper is gone; usage text no longer claims the no-database path is "plain dev" (it runs Turbopack).
@ingram-tech/nk-db@1.2.0
Minor Changes
-
c4eeaeb: Migration, pool, and coercion safety fixes:
runMigrationsis now concurrency-safe. The whole run (drift pre-flight + migrate) executes on one client holdingpg_advisory_lock— drizzle's migrator takes no lock of its own, so two concurrent deploys could both apply the same pending set and leave duplicate journal rows (permanentMigrationDriftError). The second runner now blocks, then no-ops.appliedis computed inside the lock, so it reports what the run actually did.- PGlite dev applies new migrations on every boot. Previously migrations ran only when the
.pglite/data dir didn't exist, so any migration added after the first boot was silently skipped until a data-wiping--fresh. The drizzle migrator is journal-tracked, so re-running is incremental and cheap; a custommigrateoverride must be idempotent the same way. - Local detection wins over pulled env. A
vercel env pull'dDATABASE_POOL_MAX/DATABASE_CA_CERTno longer overrides the mandatorymax: 1/ no-TLS for a local (PGlite) connection — the exact dev breakage the local branch exists to prevent.isLocalalso parses the URL hostname instead of substring-matching the whole connection string. withTx/withRlsno longer mask the real error when the failing query destroyed the connection and the rollback itself rejects.pgTimestampToIsotreats offset-less timestamps as UTC.timestamp without time zonetext (the very columns the helper exists for) was parsed in the host's local zone, shifting the instant on any non-UTC machine.- Hardening:
decode58rejects 22-char bodies that overflow 128 bits instead of silently aliasing two wire ids to one UUID;isPgErrorcaps.cause-chain depth so a cyclic chain can't hang; the journal file is Zod-validated;resetPublicTablesescapes quotes in table names;nk-pg-migrate --migrationswithout a value errors instead of eating the next flag;startPgliteDevhandles a failednext devspawn and validatesPGLITE_PORT.
@ingram-tech/nk-blog@0.1.1
Patch Changes
-
ed75401: Close the limited-MDX URL gap and harden slugs, dates, and publishing:
- Limited MDX now vets URLs.
<a href="javascript:alert(1)">, plain markdown links/images/definitions withjavascript:/data:targets, and control-char-obfuscated schemes all failed nothing before — the MDX pipeline has nourlTransform(unlike react-markdown), so Tier-1.mdxwas strictly weaker than.mdagainst exactly the LLM-authored-content threat the boundary exists for. URL-bearing attributes (href,src,poster, …) and markdown link targets are now restricted to http(s)/mailto/tel and relative paths. - Slugs are validated (
/^[a-z0-9]+(?:[-._][a-z0-9]+)*$/) in the frontmatter schema and inpublishPost, which previously interpolated the slug into the GitHub commit path unchecked —slug: "../.github/workflows/x"was a repo path-traversal vector, and looser slugs broke routes, RSS<guid>s, and JSON-LD URLs. publishPostrejects a frontmatterslugthat contradicts the publish slug. The reader routes by the frontmatter override, so the mismatch defeated the filename collision check and could break the target site's build with a duplicate-slug error.formatPostDateformats in UTC by default. Post dates are UTC midnight, so local-zone formatting rendered the previous day anywhere west of UTC (and hydration-mismatched per viewer client-side).- Duplicate-slug detection runs before the draft filter (a draft colliding with a live post now fails the production build too, matching the documented "always throws"); an empty
bespoke: {}no longer silently disables the limited-MDX boundary; same-day posts sort deterministically; the GitHub source fetches blobs with bounded concurrency (8) instead of an unboundedPromise.all; RSS escaping strips XML-invalid control characters; relative frontmatter images are absolutized in JSON-LD.
- Limited MDX now vets URLs.
-
Updated dependencies [6b188c2]
- @ingram-tech/nk-seo@0.6.0
@ingram-tech/nk-billing@0.3.0
Minor Changes
-
17760d4: Money-safety fixes across the wallet, ledger, and customer resolution:
debitBalancenow actually enforces the overspend guarantee. Stripe does not reject a debit that pushes the customer balance positive (its docstring claimed it does — a positive balance is just owed on the next invoice, which a PAYG site never issues). The debit'sending_balanceis now checked: an overdraw is reversed and throwsBillingError("insufficient_credits"). Negative/non-integeramountCents(which would silently credit the wallet) throwsRangeError, andreadBalanceclamps owed amounts to zero.- Ledger balances are numbers again.
credits_balanceis a Postgres bigint, whichpgreturns as a string;spendCredits/getBillingSummarywere leaking"90"into arithmetic and JSON. All read paths coerce to number. - Out-of-order webhook defense.
recordSubscriptionStatusaccepts the Stripe event'screatedaseventCreatedand ignores older events, so a delayedcustomer.subscription.updated: activecan no longer resurrect a subscription that a laterdeletedevent canceled. Requires the newmigrations/0002_billing_status_order.sql; withouteventCreatedthe legacy last-write-wins behavior is unchanged. - Stripe search-query escaping.
findCustomerescapes quotes/backslashes in the customer ref before interpolating into the search query — a crafted id could previously alter the query and match another tenant's customer (whose portalcreatePortalSessionwould then open). findOrCreateCustomeris idempotency-keyed on the ref, closing the duplicate-customer race thatcustomers.search's eventual consistency (up to ~1 minute stale) makes likely under double-submit.spendCreditsrejects negative/NaN costs;refundCreditsaccepts an optionaleventIdfor idempotent compensation; caller metadata can no longer overwrite the tenant ref tag on checkout sessions;fetchSubscriptionForCustomerlists up to 100 subscriptions so an older active subscription isn't missed; the README's broken guide link is replaced with real entitlement/ordering docs.