Skip to content

Releases: ingram-technologies/nextkit

@ingram-tech/nk-seo@0.6.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Minor Changes

  • 6b188c2: Fix the hreflang/canonical slice and close a URL origin escape:

    • absoluteUrl throws when the input escapes the site origin. A request to https://site//evil.com/x puts //evil.com/x into req.nextUrl.pathname and thus into x-pathname; the old pass-through resolved it to https://evil.com/x and 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 currentLocale option (the server can't see the query string).
    • The prefix strategy no longer double-prefixes. x-pathname carries the real /fr/about on a localized route; alternates were computed as /en/fr/about + /fr/fr/about. An existing locale prefix is now stripped first. strategy: "prefix" without defaultLocale throws at call time instead of silently emitting URLs no locale serves.
    • The documented middleware snippet actually works now. It set x-pathname on 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 the NextResponse.next({ request: { headers } }) form, note that copying req.headers first neutralizes client spoofing, and document the static-rendering tradeoff of the header path.
    • noIndex: true emits follow: 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 both next and react at runtime).

@ingram-tech/nk-marketing@0.3.0

Choose a tag to compare

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-side WIRE_API_KEY, instead of hand-rolling the fetch in every repo.

@ingram-tech/nk-marketing@0.2.4

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Patch Changes

  • 51d7812: nk-i18n:

    • negotiateAcceptLanguage honors q-values per RFC 9110: the highest quality wins instead of raw header order, and a q=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 bad fr entry 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 MissingKeysPolicy is documented as reserved/not-yet-consumed.

    nk-email:

    • fromAddress validates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).
    • buildListUnsubscribeHeaders rejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.
    • DEFAULT_TIMEOUT_MS is 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/subscribe validate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.
    • A failing releaseDelivery can no longer abort the rest of a broadcast batch or mask the original send error in sendLifecycle.
    • 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

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Patch Changes

  • 51d7812: nk-i18n:

    • negotiateAcceptLanguage honors q-values per RFC 9110: the highest quality wins instead of raw header order, and a q=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 bad fr entry 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 MissingKeysPolicy is documented as reserved/not-yet-consumed.

    nk-email:

    • fromAddress validates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).
    • buildListUnsubscribeHeaders rejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.
    • DEFAULT_TIMEOUT_MS is 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/subscribe validate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.
    • A failing releaseDelivery can no longer abort the rest of a broadcast batch or mask the original send error in sendLifecycle.
    • 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

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Patch Changes

  • 51d7812: nk-i18n:

    • negotiateAcceptLanguage honors q-values per RFC 9110: the highest quality wins instead of raw header order, and a q=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 bad fr entry 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 MissingKeysPolicy is documented as reserved/not-yet-consumed.

    nk-email:

    • fromAddress validates the local part with the same header-injection guard as the display name (it was interpolated raw into the address).
    • buildListUnsubscribeHeaders rejects values containing control characters, angle brackets, or commas, which would silently corrupt the RFC 8058 header pair.
    • DEFAULT_TIMEOUT_MS is 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/subscribe validate the email up front with a descriptive error (mirroring the migration's check constraint) instead of surfacing a raw Postgres constraint violation.
    • A failing releaseDelivery can no longer abort the rest of a broadcast batch or mask the original send error in sendLifecycle.
    • 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

Choose a tag to compare

Minor Changes

  • 79bcb7d: Toolchain consolidation:

    • nk no longer formats SQL. nk format/nk check run oxfmt only; the prettier + prettier-plugin-sql dependencies are dropped. SQL in these repos is ~all generated (drizzle migrations, pg_dump baselines, pglite fixtures), so formatting it only churned generated files and crashed on psql directives (\restrict) for no gain.
    • nk test — new passthrough for vitest run (extra args forwarded), completing the set alongside lint/format/check/type-check/build.
    • nk doctor [--fix] — reports a site's drift from the canonical nk-dev toolchain (scripts not pointing at nk, superseded deps, .oxlintrc.json/tsconfig.json extends not pointing at nk-dev, a missing CLAUDE.md guide import, stale knip.json ignores, a dead .prettierignore) and, with --fix, applies the mechanical corrections. Exits non-zero on model-breaking findings so it can gate CI.
    • nk check warns 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 at nk 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

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Patch Changes

  • d430473: Pre-commit hook and exit-code correctness:

    • format-staged no longer commits unstaged hunks. oxfmt --write rewrites the working tree and the re-git add swept everything into the commit — including hunks deliberately left out with git add -p. Partially staged files are now skipped with a warning.
    • Non-ASCII filenames are formatted again. git diff --name-only octal-escapes them ("\303\251 test.ts"), which matched no real path, so such files were silently never formatted or re-staged; same fix in nk format's SQL file listing. Both now use -z/NUL splitting.
    • Signal-killed tools fail the gate. run()/nk dev treated a null exit status (OOM-kill, SIGSEGV) as success, letting a crashed linter pass nk check.
    • nk check reads the SQL result from formatSql's return value instead of the process.exitCode global (which misattributed any earlier failure to SQL); SQL formatting defaults carry the house tabWidth: 4 / printWidth: 88 (Prettier's own 80/2 applied before); deleted-but-tracked .sql files no longer crash nk format; the hook uses existsSync instead of spawning the Unix test binary per file; the unused capture() 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

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Minor Changes

  • c4eeaeb: Migration, pool, and coercion safety fixes:

    • runMigrations is now concurrency-safe. The whole run (drift pre-flight + migrate) executes on one client holding pg_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 (permanent MigrationDriftError). The second runner now blocks, then no-ops. applied is 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 custom migrate override must be idempotent the same way.
    • Local detection wins over pulled env. A vercel env pull'd DATABASE_POOL_MAX/DATABASE_CA_CERT no longer overrides the mandatory max: 1 / no-TLS for a local (PGlite) connection — the exact dev breakage the local branch exists to prevent. isLocal also parses the URL hostname instead of substring-matching the whole connection string.
    • withTx/withRls no longer mask the real error when the failing query destroyed the connection and the rollback itself rejects.
    • pgTimestampToIso treats offset-less timestamps as UTC. timestamp without time zone text (the very columns the helper exists for) was parsed in the host's local zone, shifting the instant on any non-UTC machine.
    • Hardening: decode58 rejects 22-char bodies that overflow 128 bits instead of silently aliasing two wire ids to one UUID; isPgError caps .cause-chain depth so a cyclic chain can't hang; the journal file is Zod-validated; resetPublicTables escapes quotes in table names; nk-pg-migrate --migrations without a value errors instead of eating the next flag; startPgliteDev handles a failed next dev spawn and validates PGLITE_PORT.

@ingram-tech/nk-blog@0.1.1

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

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 with javascript:/data: targets, and control-char-obfuscated schemes all failed nothing before — the MDX pipeline has no urlTransform (unlike react-markdown), so Tier-1 .mdx was strictly weaker than .md against 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 in publishPost, 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.
    • publishPost rejects a frontmatter slug that 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.
    • formatPostDate formats 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 unbounded Promise.all; RSS escaping strips XML-invalid control characters; relative frontmatter images are absolutized in JSON-LD.
  • Updated dependencies [6b188c2]

    • @ingram-tech/nk-seo@0.6.0

@ingram-tech/nk-billing@0.3.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 00:42
8f8bfc8

Minor Changes

  • 17760d4: Money-safety fixes across the wallet, ledger, and customer resolution:

    • debitBalance now 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's ending_balance is now checked: an overdraw is reversed and throws BillingError("insufficient_credits"). Negative/non-integer amountCents (which would silently credit the wallet) throws RangeError, and readBalance clamps owed amounts to zero.
    • Ledger balances are numbers again. credits_balance is a Postgres bigint, which pg returns as a string; spendCredits/getBillingSummary were leaking "90" into arithmetic and JSON. All read paths coerce to number.
    • Out-of-order webhook defense. recordSubscriptionStatus accepts the Stripe event's created as eventCreated and ignores older events, so a delayed customer.subscription.updated: active can no longer resurrect a subscription that a later deleted event canceled. Requires the new migrations/0002_billing_status_order.sql; without eventCreated the legacy last-write-wins behavior is unchanged.
    • Stripe search-query escaping. findCustomer escapes 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 portal createPortalSession would then open).
    • findOrCreateCustomer is idempotency-keyed on the ref, closing the duplicate-customer race that customers.search's eventual consistency (up to ~1 minute stale) makes likely under double-submit.
    • spendCredits rejects negative/NaN costs; refundCredits accepts an optional eventId for idempotent compensation; caller metadata can no longer overwrite the tenant ref tag on checkout sessions; fetchSubscriptionForCustomer lists 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.