milestone 23 s2: react aria primitives and their cluster
This commit is contained in:
+95
-17
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user