Skip to content

Commit 6f65a00

Browse files
committed
fix(ci): enforce mirror set structure and resolve all dependency specifier forms
1 parent 0fe83c1 commit 6f65a00

5 files changed

Lines changed: 822 additions & 21 deletions

File tree

‎.github/mirror-lock.json‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"atlas-pipeline": {
33
"packages/polycss/src/render/atlas/emit.ts": "67ee80a028a4b64405511ea36f63f14173ae6963e6a5fd39edea7699b760626b",
4+
"packages/polycss/src/render/atlas/index.ts": "873e654eb976f3cf29e84cfdf281213a895ead2be81db4c54cd046190033cf3f",
45
"packages/polycss/src/render/atlas/packing.ts": "8742b14b1a5bcb33413d3d28c3f6e0f6844cd572158fc71b00eced2aa9f800a0",
56
"packages/polycss/src/render/atlas/paintDefaults.ts": "0e2ee4b8ef96cba53eba9a0c2d959111d66c453a99c06ab857768b05180be0f9",
67
"packages/polycss/src/render/atlas/plan.ts": "6fc385814a2339dbf3001c57ad6c0e8c0edcf5f989fffcebd2e27a1fc0a2ea2c",
@@ -9,20 +10,35 @@
910
"packages/polycss/src/render/atlas/solidTrianglePlan.ts": "386c3cff4a9e6848af2584f9cabef92ba623bf6ef78ab64bed59024add643e95",
1011
"packages/polycss/src/render/atlas/stableTriangle.ts": "ba780fcf1177a8ec338a67e8900ee2d4032cc2a6e731329782b01107e79f1463",
1112
"packages/polycss/src/render/atlas/strategy.ts": "890b9487272c469f3c3dfd8f9a9fc7b66b403a9200ac27ca2698b6c19bf82605",
13+
"packages/polycss/src/render/atlas/types.ts": "3ccda1855741fce7d7d4c9073b77151552460824bab4c03aa8a674a0b7288b07",
14+
"packages/react/src/scene/atlas/atlasPoly.tsx": "35ea8fcdbc068e5f867e5d8bac59300e73700a7d91310fc5db53ffb81f5e6297",
15+
"packages/react/src/scene/atlas/borderShape.tsx": "011a549b7a135238317c0235a387539529cc44315cd210846d98fc8e886ba5ec",
1216
"packages/react/src/scene/atlas/buildAtlasPages.ts": "5ca0059c936d3948fd91f8888acb3f19534e327075367528517879f85ee4a3e1",
17+
"packages/react/src/scene/atlas/cornerShapeSolid.tsx": "f531d98803ac5e7c27ef2bb8cd2644a0676987c27b5a1bff872e7380696e524c",
1318
"packages/react/src/scene/atlas/detection.ts": "faf6fd923a246e1cd42b1b1a42e6ee92d9cc637ec4fc8c852672a1fc4deb9331",
1419
"packages/react/src/scene/atlas/filterPlans.ts": "b8004f3c1586a6a9d65553a239c3c4d5c05c5f29ddd19c0278a7c73af776e1a1",
20+
"packages/react/src/scene/atlas/index.tsx": "60d6c1566f588070f86dae005a42b5fc169a8732aa3d7d4722a785c549a45e7d",
1521
"packages/react/src/scene/atlas/packing.ts": "ec59c5a92abe3caeaf1bf8589e8aab2b62b63c94955b77aabfd74c6ea6326fa3",
1622
"packages/react/src/scene/atlas/paintDefaults.ts": "0300239470371dc4f454c7516ab7505a64f68f94b83f5d72fef31ea900e504e6",
23+
"packages/react/src/scene/atlas/projectiveSolid.tsx": "5b1a095c1351923119c462c43d0cf85d2ac7a4da9908900447aa93ce31abb89f",
1724
"packages/react/src/scene/atlas/solidTriangleStyle.ts": "d7ee521f08fdc71370f7f5770a30dfa5e11526e98e5e4b351416b389126997cc",
1825
"packages/react/src/scene/atlas/stableTriangleDom.ts": "d482c608c3dd48455f3534fa69e2792550a56e8fb801da7fe07f223ece315920",
26+
"packages/react/src/scene/atlas/triangle.tsx": "bbf16eea216047c840dbfa9ecebb9a93f49e72f6c909fa28288e097aad0c13ce",
27+
"packages/react/src/scene/atlas/useTextureAtlas.ts": "667eed756e09a245499491c86253b4772111bae8e22fe15e325d5848f2548736",
28+
"packages/vue/src/scene/atlas/atlasPoly.ts": "12df1400205c7fcfb6cee680d23a9354333592132eb4ab5799fe61974676c1e2",
29+
"packages/vue/src/scene/atlas/borderShape.ts": "225ea88c0c7b2484f087e02ea57a7fd8e6963cc514cc2b505197d62e610d2874",
1930
"packages/vue/src/scene/atlas/buildAtlasPages.ts": "5ca0059c936d3948fd91f8888acb3f19534e327075367528517879f85ee4a3e1",
31+
"packages/vue/src/scene/atlas/cornerShapeSolid.ts": "ceb20aff84747f8a61d287feb4586c584cc0b8e40e59d52e883d64869ec5f015",
2032
"packages/vue/src/scene/atlas/detection.ts": "faf6fd923a246e1cd42b1b1a42e6ee92d9cc637ec4fc8c852672a1fc4deb9331",
2133
"packages/vue/src/scene/atlas/filterPlans.ts": "b8004f3c1586a6a9d65553a239c3c4d5c05c5f29ddd19c0278a7c73af776e1a1",
34+
"packages/vue/src/scene/atlas/index.ts": "b8a89b79dbb7fc008715d9dc71e1338600b2e6e8557a6e576261391387f86fe0",
2235
"packages/vue/src/scene/atlas/packing.ts": "ec59c5a92abe3caeaf1bf8589e8aab2b62b63c94955b77aabfd74c6ea6326fa3",
2336
"packages/vue/src/scene/atlas/paintDefaults.ts": "0300239470371dc4f454c7516ab7505a64f68f94b83f5d72fef31ea900e504e6",
37+
"packages/vue/src/scene/atlas/projectiveSolid.ts": "dbeb573f4e8cbe9c5ad582ac3be92630f259597f4fcfa149d71f0280629d57ac",
2438
"packages/vue/src/scene/atlas/solidTriangleStyle.ts": "81443b1042b7fd9f8e2002577f684834e5441216a35705497d4690385b983bbe",
25-
"packages/vue/src/scene/atlas/stableTriangleDom.ts": "0e54a1b1b7f258be855b6a7c450c7bb471c20cbd504a6a2adb00f8cf899c1454"
39+
"packages/vue/src/scene/atlas/stableTriangleDom.ts": "0e54a1b1b7f258be855b6a7c450c7bb471c20cbd504a6a2adb00f8cf899c1454",
40+
"packages/vue/src/scene/atlas/triangle.ts": "fab6503150bd5edee71db7c595ded006deb9b6977209eb78360fd7edfd4ba462",
41+
"packages/vue/src/scene/atlas/useTextureAtlas.ts": "e6a1b380cbe805361087188d23b23b51cfcdb195ef24f1aeced838ac7dddbbda"
2642
},
2743
"voxel-renderer": {
2844
"packages/polycss/src/render/voxelRenderer.ts": "6a74ebb25fcdd788adb7a7be77d2d0613992bdba7c877a905995e10a85485aee",
@@ -35,15 +51,15 @@
3551
"packages/react/src/scene/mesh/useMeshEvents.ts": "1c88e709cc3965636b7e983ce9198c090f12e539e75bb5afcc00b593cdb2de9d",
3652
"packages/react/src/scene/mesh/useMeshGeometry.ts": "ce7c66c48e80e7579c068cb9b2c42096a3fde34fc237bed4c4715d274f8874ea",
3753
"packages/react/src/scene/mesh/useMeshLighting.ts": "b36e5e0fbeee9b7429a83b09ebe4249e64ed33f4aea0dfd140c65f9a737af0bb",
38-
"packages/react/src/scene/mesh/useReceiverShadows.tsx": "db73e7c1aaf9eaf003e98c011a0b997b2a9042488efe6c66eeb1ac36a31aa748",
54+
"packages/react/src/scene/mesh/useReceiverShadows.tsx": "3cccee2ff6ad8a6b7a54aa250d4b2c47efaaed2168ebea68bd29a51db2424186",
3955
"packages/react/src/scene/mesh/useStableDom.ts": "29eca9ed7e30bc235b8885b65706fe75b41237bff23bdb689040f98ff3f2a60f",
4056
"packages/react/src/scene/mesh/useVoxelFastPath.ts": "e4fd58487557e67c8576b327452db91b357c947bf18a5761efe60a07d5ef2bd1",
4157
"packages/vue/src/scene/mesh/useGroundShadow.ts": "ec91a425a04e0424653c6c5cc7a3f2ca3f116788c4234d2fdf808c02caf90770",
4258
"packages/vue/src/scene/mesh/useMeshAtlas.ts": "a0f9532f9b770c9d382382b87d546cb7c13086af3c1e6154564af5597015889b",
4359
"packages/vue/src/scene/mesh/useMeshEvents.ts": "81874ccad4827e21cf891fd0b8512b87c1b6f304b7c7476f79539da7f8f103f2",
4460
"packages/vue/src/scene/mesh/useMeshGeometry.ts": "e1518f576fa8724218967996fee89ced0175fa044b1587d8e2d9693e7bac1e52",
4561
"packages/vue/src/scene/mesh/useMeshLighting.ts": "885b8caaba5955d3668135bf4b124a4c32052c5f05f30574f371498470a6646d",
46-
"packages/vue/src/scene/mesh/useReceiverShadows.ts": "3102a66b46b28ee763e7964584631c92dc0b640b8c04cc59d07ae240a58a207c",
62+
"packages/vue/src/scene/mesh/useReceiverShadows.ts": "930fc3d3debf11ffb52cd6a0922aa308a06a4a073acc5b2c4869cf0b944f9fbc",
4763
"packages/vue/src/scene/mesh/useStableDom.ts": "cc92fd16d0c9f87d3cc8762004122da26a21ff8b123249ce79b45bbd9a380042",
4864
"packages/vue/src/scene/mesh/useVoxelFastPath.ts": "dc4d5f8c46c90b7232d8a3b665fc281f5f03e4d9ab0c461db541c403a0b474e6"
4965
},

‎.github/scripts/check-boundaries.mjs‎

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
* name something outside its allow-list fails even if no source file imports
1616
* it yet. (`devDependencies` are tooling and are not constrained.) Manifest
1717
* checking resolves the dependency VALUE — `npm:` aliases, `link:`/`file:`/
18-
* `portal:` local paths, `workspace:` aliases — because the key is not the
19-
* package that gets installed.
18+
* `portal:` local paths, `workspace:` aliases and `workspace:` relative paths
19+
* — because the key is not the package that gets installed. Specifier forms
20+
* whose target cannot be determined from the repo (`catalog:`, git specs,
21+
* tarball URLs, `jsr:`, unknown protocols) are reported as violations rather
22+
* than falling back to the key, since falling back to the key IS the bypass.
2023
*
2124
* Also forbids, everywhere in the repo, deep imports into another package's
2225
* src (e.g. `@layoutit/polycss-core/src/...` or `../../packages/x/src/...`)
@@ -128,20 +131,70 @@ function readLocalPackageName(packageDir, relPath) {
128131
}
129132
}
130133

134+
/**
135+
* A `workspace:` / `link:` target that is a path rather than a package name.
136+
* `workspace:../polycss` and `workspace:packages/polycss` both mount a
137+
* different local package under the declared key; only a leading `@` marks a
138+
* scoped package NAME rather than a path.
139+
*/
140+
const isLocalPathSpec = (rest) =>
141+
rest.startsWith(".") ||
142+
rest.startsWith("/") ||
143+
rest.startsWith("~/") ||
144+
(rest.includes("/") && !rest.startsWith("@"));
145+
146+
/**
147+
* A bare semver range or range operator, which leaves the key as the installed
148+
* package: `^`, `*`, `~`, `1.2.3`, `^0.2.0`, `>=1 <2`, `1.x`. Anything else
149+
* after `workspace:` is a package name — with or without an `@range` suffix.
150+
*/
151+
const isVersionRange = (rest) => /^[\s*^~><=v\d.|xX+-]+$/.test(rest);
152+
153+
/** Specifier protocols whose install target this checker cannot determine. */
154+
const UNRESOLVABLE_PROTOCOLS = new Map([
155+
[
156+
"catalog",
157+
"is a pnpm catalog reference whose target lives in pnpm-workspace.yaml",
158+
],
159+
[
160+
"jsr",
161+
"is a JSR specifier, which installs under a rewritten npm name (@jsr/…)",
162+
],
163+
["git", "is a git specifier, whose installed package name is in the repo"],
164+
["github", "is a git specifier, whose installed package name is in the repo"],
165+
["gitlab", "is a git specifier, whose installed package name is in the repo"],
166+
[
167+
"bitbucket",
168+
"is a git specifier, whose installed package name is in the repo",
169+
],
170+
["gist", "is a git specifier, whose installed package name is in the repo"],
171+
["http", "is a remote tarball, whose package name is inside the tarball"],
172+
["https", "is a remote tarball, whose package name is inside the tarball"],
173+
]);
174+
131175
/**
132176
* The manifest KEY is not the package that gets installed. `npm:` aliases,
133-
* `link:`/`file:`/`portal:` local paths and `workspace:<name>@<range>` aliases
134-
* all mount a DIFFERENT package under the declared key, so
135-
* `"@layoutit/polycss-core": "npm:@layoutit/polycss@0.2.0"` would pass a
136-
* key-only allow-list while installing the forbidden graph. The allow-list is
137-
* therefore applied to the resolved target, not the key.
177+
* `link:`/`file:`/`portal:` local paths, `workspace:<name>[@<range>]` aliases
178+
* and `workspace:<path>` targets all mount a DIFFERENT package under the
179+
* declared key, so `"@layoutit/polycss-core": "npm:@layoutit/polycss@0.2.0"`
180+
* would pass a key-only allow-list while installing the forbidden graph. The
181+
* allow-list is therefore applied to the resolved target, not the key.
138182
*
139-
* A local path whose target cannot be read is `unresolved` rather than
140-
* allowed: an unverifiable target is not a permitted one.
183+
* Anything this function cannot resolve is `unresolved`, which the caller
184+
* turns into a violation. That is deliberate: an unverifiable target is not a
185+
* permitted one, and the alternative — falling back to the key — is precisely
186+
* the bypass. Catalog references, git specs and tarball URLs therefore FAIL
187+
* with an explanation rather than passing; a package that needs one must
188+
* declare it in a form the checker can see through.
141189
*/
142190
export function resolveDependencyTarget(name, spec, options = {}) {
143-
if (typeof spec !== "string") return { name };
191+
if (typeof spec !== "string") {
192+
return { unresolved: "has a non-string version specifier" };
193+
}
144194
const value = spec.trim();
195+
if (value.length === 0) {
196+
return { unresolved: "has an empty version specifier" };
197+
}
145198

146199
if (value.startsWith("npm:")) {
147200
const target = splitNameAtRange(value.slice("npm:".length));
@@ -164,13 +217,47 @@ export function resolveDependencyTarget(name, spec, options = {}) {
164217

165218
if (value.startsWith("workspace:")) {
166219
const rest = value.slice("workspace:".length);
167-
// `workspace:^`, `workspace:*`, `workspace:1.2.3` keep the key; only the
168-
// `workspace:<other-name>@<range>` alias form retargets it.
169-
if (/^[@a-zA-Z]/.test(rest) && rest.includes("@", 1)) {
170-
const target = splitNameAtRange(rest);
171-
if (target) return { name: target, via: `workspace alias "${value}"` };
220+
if (rest.length === 0) {
221+
return { unresolved: 'has an empty "workspace:" specifier' };
172222
}
173-
return { name };
223+
if (isLocalPathSpec(rest)) {
224+
const target = readLocalPackageName(options.packageDir, rest);
225+
return target
226+
? { name: target, via: `workspace path target ${rest}` }
227+
: {
228+
unresolved:
229+
`points at workspace path "${value}", whose package.json name ` +
230+
"could not be read",
231+
};
232+
}
233+
// `workspace:^`, `workspace:*`, `workspace:1.2.3` keep the key; every
234+
// other form names a package, whether or not it carries an `@range`.
235+
if (isVersionRange(rest)) return { name };
236+
const target = splitNameAtRange(rest);
237+
return target
238+
? { name: target, via: `workspace alias "${value}"` }
239+
: { unresolved: `has an unparseable workspace alias "${value}"` };
240+
}
241+
242+
const protocol = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(value);
243+
if (protocol) {
244+
const base = protocol[1].toLowerCase().split("+")[0];
245+
const known = UNRESOLVABLE_PROTOCOLS.get(base);
246+
return {
247+
unresolved: known
248+
? `${known}, so the installed package cannot be verified here`
249+
: `uses an unrecognised specifier protocol "${protocol[1]}:"`,
250+
};
251+
}
252+
253+
// npm's `owner/repo` shorthand is a git dependency. A semver range or a
254+
// dist-tag never contains a slash, so this is unambiguous.
255+
if (value.includes("/")) {
256+
return {
257+
unresolved:
258+
`looks like a git shorthand ("${value}"), whose installed package ` +
259+
"name is inside the repository",
260+
};
174261
}
175262

176263
return { name };

‎.github/scripts/check-boundaries.test.mjs‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,126 @@ test("workspace ranges and aliases onto allowed packages still pass", () => {
327327
assert.deepEqual(runChecks(root), []);
328328
cleanup(root);
329329
});
330+
331+
test("a workspace: relative path resolves to the package it points at", () => {
332+
const root = makeRepo(
333+
withManifests(
334+
{},
335+
{
336+
react: {
337+
name: "@layoutit/polycss-react",
338+
// Allowed KEY, forbidden install target, and no `@range` anywhere
339+
// for the alias parser to notice.
340+
dependencies: { "@layoutit/polycss-core": "workspace:../polycss" },
341+
},
342+
polycss: { name: "@layoutit/polycss" },
343+
},
344+
),
345+
);
346+
const violations = runChecks(root);
347+
assert.equal(violations.length, 1);
348+
assert.match(
349+
violations[0],
350+
/resolves to disallowed package "@layoutit\/polycss" via workspace path target/,
351+
);
352+
cleanup(root);
353+
});
354+
355+
test("an un-versioned workspace alias is resolved, not treated as a range", () => {
356+
for (const spec of ["workspace:@layoutit/polycss", "workspace:@layoutit/polycss@*"]) {
357+
const root = makeRepo(
358+
withManifests(
359+
{},
360+
{
361+
vue: {
362+
name: "@layoutit/polycss-vue",
363+
dependencies: { "@layoutit/polycss-core": spec },
364+
},
365+
},
366+
),
367+
);
368+
const violations = runChecks(root);
369+
assert.equal(violations.length, 1, spec);
370+
assert.match(
371+
violations[0],
372+
/resolves to disallowed package "@layoutit\/polycss" via workspace alias/,
373+
spec,
374+
);
375+
cleanup(root);
376+
}
377+
});
378+
379+
test("a workspace path that cannot be read is not assumed allowed", () => {
380+
const root = makeRepo(
381+
withManifests(
382+
{},
383+
{
384+
react: {
385+
name: "@layoutit/polycss-react",
386+
dependencies: { "@layoutit/polycss-core": "workspace:../nowhere" },
387+
},
388+
},
389+
),
390+
);
391+
const violations = runChecks(root);
392+
assert.equal(violations.length, 1);
393+
assert.match(violations[0], /whose package.json name could not be read/);
394+
cleanup(root);
395+
});
396+
397+
test("specifier forms with an undeterminable target fail instead of passing", () => {
398+
const unresolvable = [
399+
["catalog:", /pnpm catalog reference/],
400+
["catalog:default", /pnpm catalog reference/],
401+
["jsr:@layoutit/polycss", /JSR specifier/],
402+
["github:layoutit/polycss", /git specifier/],
403+
["git+https://github.com/layoutit/polycss.git", /git specifier/],
404+
["git+ssh://git@github.com/layoutit/polycss.git", /git specifier/],
405+
["https://example.com/polycss-0.1.0.tgz", /remote tarball/],
406+
["layoutit/polycss", /git shorthand/],
407+
["layoutit/polycss#v1", /git shorthand/],
408+
["mystery:whatever", /unrecognised specifier protocol/],
409+
["workspace:", /empty "workspace:" specifier/],
410+
["", /empty version specifier/],
411+
[42, /non-string version specifier/],
412+
];
413+
for (const [spec, pattern] of unresolvable) {
414+
const resolved = resolveDependencyTarget("@layoutit/polycss-core", spec, {});
415+
assert.equal(resolved.name, undefined, `${spec} must not resolve to a name`);
416+
assert.match(resolved.unresolved, pattern, String(spec));
417+
418+
// And the resolver's verdict must reach the manifest check as a violation,
419+
// even though the KEY itself is allow-listed.
420+
const violations = checkManifestDependencies(
421+
{ dependencies: { "@layoutit/polycss-core": spec } },
422+
"react",
423+
PACKAGE_RULES.react,
424+
{},
425+
);
426+
assert.equal(violations.length, 1, String(spec));
427+
assert.match(violations[0], /so the allow-list cannot be applied/, String(spec));
428+
}
429+
});
430+
431+
test("plain ranges, dist-tags and workspace ranges keep the declared key", () => {
432+
const keep = [
433+
"^0.2.0",
434+
"0.2.0",
435+
"*",
436+
">=1.0.0 <2.0.0",
437+
"1.x",
438+
"latest",
439+
"next",
440+
"workspace:^",
441+
"workspace:*",
442+
"workspace:~",
443+
"workspace:1.2.3",
444+
"workspace:^1.2.3",
445+
"workspace:>=1.0.0 <2.0.0",
446+
];
447+
for (const spec of keep) {
448+
const resolved = resolveDependencyTarget("@layoutit/polycss-core", spec, {});
449+
assert.equal(resolved.name, "@layoutit/polycss-core", spec);
450+
assert.equal(resolved.unresolved, undefined, spec);
451+
}
452+
});

0 commit comments

Comments
 (0)