milestone 23 s2: react aria primitives and their cluster

This commit is contained in:
2026-08-12 22:12:18 +02:00
parent 994bbf922c
commit 01e455c8af
32 changed files with 2316 additions and 532 deletions
+95 -17
View File
@@ -66,6 +66,13 @@ const npm_not_shipped = [_][]const u8{
"invariant",
"js-tokens",
"loose-envify",
"@internationalized/date",
"@internationalized/number",
"@react-types/shared",
"@swc/helpers",
"aria-hidden",
"client-only",
"tslib",
};
/// Components the shipped artifacts contain that no dependency file mentions,
@@ -112,15 +119,61 @@ const ZigDependencyVersion = struct {
},
};
/// The licence every *shipped* npm package carries today, and the licence of
/// every npm text under `licenses/`. The identity file records a licence token
/// per package; nothing used to read it, so a package that relicensed passed as
/// long as its version had not moved. A token other than this one needs its own
/// licence text and a decision about EUPL-1.2 compatibility, which is a human
/// review, not a paste. The `npm_not_shipped` packages are exempt — they
/// redistribute nothing, and one of them is Unlicense.
/// The licence each npm package of the recorded sets carries today. The identity
/// file records a licence token per package; nothing used to read it, so a
/// package that relicensed passed as long as its version had not moved.
///
/// MIT is the default because almost every package here is MIT, so only the
/// exceptions are written down. A package whose recorded token stops matching
/// its expectation fails — including the packages below, which is the point of
/// naming them rather than waving them through: a new licence on any of them is
/// a new decision, not a fact to absorb quietly.
///
/// Adding a name here is a human review, not a paste. A licence this project has
/// not taken before needs its own text under `licenses/`, an inventory entry
/// that says who accepted it and when, and whatever that licence's attribution
/// terms require. Apache-2.0 arrived that way: Mokhtar Mial accepted it inbound
/// on 2026-08-12, and `licenses/react-aria-apache-2.0.txt` carries the text
/// Section 4 asks for.
///
/// Packages that put no byte in `web/dist` are here too. They redistribute
/// nothing today, so their licence carries no obligation today — but "not
/// shipped" is a claim about the build that a future build can falsify, and
/// recording the licence now means the answer is already reviewed when it does.
const npm_expected_licence = "MIT";
const npm_licence_exceptions = [_]NpmLicence{
// Shipped: React Aria and the one @internationalized package it pulls into
// the bundle.
.{ .name = "react-aria-components", .licence = "Apache-2.0" },
.{ .name = "react-aria", .licence = "Apache-2.0" },
.{ .name = "react-stately", .licence = "Apache-2.0" },
.{ .name = "@internationalized/string", .licence = "Apache-2.0" },
// Not shipped: the rest of the React Aria closure.
.{ .name = "@internationalized/date", .licence = "Apache-2.0" },
.{ .name = "@internationalized/number", .licence = "Apache-2.0" },
.{ .name = "@react-types/shared", .licence = "Apache-2.0" },
.{ .name = "@swc/helpers", .licence = "Apache-2.0" },
.{ .name = "tslib", .licence = "0BSD" },
// Not shipped: reached only through @tanstack/router-core's server paths
// and @stylexjs/stylex's compiler.
.{ .name = "isbot", .licence = "Unlicense" },
.{ .name = "css-mediaquery", .licence = "BSD" },
};
const NpmLicence = struct {
name: []const u8,
licence: []const u8,
};
/// The reviewed licence for one package: its exception, or MIT.
fn expectedLicence(name: []const u8) []const u8 {
for (npm_licence_exceptions) |entry| {
if (std.mem.eql(u8, entry.name, name)) return entry.licence;
}
return npm_expected_licence;
}
/// The two licence texts that must be reproduced in full, pinned by content.
/// The marker-string probes below prove the right *document* is present; they
/// cannot tell a complete Apache-2.0 from one with its middle sections deleted,
@@ -490,22 +543,21 @@ test "the CA bundle entry names the Alpine release the Dockerfile pins" {
}
}
test "every npm package in the recorded closure ships under the licence the inventory assumes" {
test "every npm package in the recorded closure carries the licence the inventory expects" {
for ([_][]const u8{ npm_section, generator_section }) |header| {
const recorded = try recordedSection(header);
var lines = std.mem.tokenizeScalar(u8, recorded, '\n');
while (lines.next()) |line| {
const pkg = parseRecordedPackage(line) orelse return error.MalformedIdentityLine;
// A package that puts no byte in web/dist redistributes nothing, so
// its licence carries no obligation. isbot is Unlicense and is one
// of these.
if (isNotShipped(pkg.name)) continue;
if (!std.mem.eql(u8, pkg.licence, npm_expected_licence)) {
const expected = expectedLicence(pkg.name);
if (!std.mem.eql(u8, pkg.licence, expected)) {
std.debug.print(
"npm package '{s}' is recorded under '{s}', not {s}. Every npm licence text under" ++
" licenses/ is an {s} text, so this package needs one of its own and a" ++
" decision about EUPL-1.2 compatibility.\n",
.{ pkg.name, pkg.licence, npm_expected_licence, npm_expected_licence },
"npm package '{s}' is recorded under '{s}', but the reviewed expectation is" ++
" '{s}'. Work out what the new licence means for the shipped artifacts, give" ++
" it a text under licenses/ and an inventory entry naming who accepted it if" ++
" this project has not taken it before, then record the new expectation in" ++
" npm_licence_exceptions.\n",
.{ pkg.name, pkg.licence, expected },
);
return error.NpmLicenceChanged;
}
@@ -513,6 +565,32 @@ test "every npm package in the recorded closure ships under the licence the inve
}
}
test "every npm licence exception names a package the identity file records" {
const closure = try recordedSection(npm_section);
const generators = try recordedSection(generator_section);
for (npm_licence_exceptions) |entry| {
// A stale exception is how a package quietly regains the MIT default
// after it leaves the tree and comes back under a different licence.
if (recordsPackage(closure, entry.name) or recordsPackage(generators, entry.name)) continue;
std.debug.print(
"npm_licence_exceptions names '{s}', which licenses/dependency-identity.txt no longer" ++
" records. Drop the exception when the package leaves the closure.\n",
.{entry.name},
);
return error.StaleLicenceException;
}
}
/// Whether a recorded `<package> <version> <licence>` section names a package.
fn recordsPackage(section: []const u8, name: []const u8) bool {
var lines = std.mem.tokenizeScalar(u8, section, '\n');
while (lines.next()) |line| {
const pkg = parseRecordedPackage(line) orelse continue;
if (std.mem.eql(u8, pkg.name, name)) return true;
}
return false;
}
test "the licence texts that must be reproduced in full are unmodified" {
for (pinned_texts) |pinned| {
const body = textBody(pinned.file) orelse {