@multiplatform.one/config 7.7.1 → 7.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lint.mjs +104 -1
- package/package.json +2 -2
package/lint.mjs
CHANGED
|
@@ -37,7 +37,18 @@
|
|
|
37
37
|
* target resolves, where react-native-web exports no X. The alias makes
|
|
38
38
|
* that a module-evaluation SyntaxError, which takes the whole preview
|
|
39
39
|
* bundle down rather than one component.
|
|
40
|
-
* 9. Fail on
|
|
40
|
+
* 9. Fail on a `.react-flow__` SELECTOR outside the token bridge (MPO-191) —
|
|
41
|
+
* React Flow paints through `var(--xy-thing, var(--xy-thing-default))`, so
|
|
42
|
+
* every colour, width and shadow is settable without touching its
|
|
43
|
+
* stylesheet. A hand-written `.react-flow__` rule anywhere else forks that
|
|
44
|
+
* paint: it wins or loses on specificity rather than on the knob, and the
|
|
45
|
+
* next library upgrade silently changes which. The whole point of the
|
|
46
|
+
* bridge is that a designer never touches React Flow CSS — including us —
|
|
47
|
+
* so the rule is mechanical. `theme/graphVars.ts` (the map) and the
|
|
48
|
+
* package's own `css/` tree (outside `src`) are the two places it may live.
|
|
49
|
+
* Comments and specs are exempt; strings are not, because a hand-written
|
|
50
|
+
* rule lives in a template literal.
|
|
51
|
+
* 10. Fail on an `import.meta` TOKEN in public/<pkg>/src (MPO-204) — in a
|
|
41
52
|
* classic script it is a PARSE-time SyntaxError, and two bundles execute
|
|
42
53
|
* mpo as one (the vxrn/rolldown Hermes bundle, and any Metro bundle for
|
|
43
54
|
* platform=web, i.e. an Expo DOM component). Node-only build tooling is
|
|
@@ -1183,6 +1194,88 @@ function checkImportMetaInPublicSource() {
|
|
|
1183
1194
|
return violations;
|
|
1184
1195
|
}
|
|
1185
1196
|
|
|
1197
|
+
// ── React Flow selectors outside the token bridge (MPO-191) ───────────────
|
|
1198
|
+
|
|
1199
|
+
const REACT_FLOW_SELECTOR_RE = /\.react-flow__[\w-]+/g;
|
|
1200
|
+
/**
|
|
1201
|
+
* The two files a `.react-flow__` string may live in, both DECLARED boundaries.
|
|
1202
|
+
*
|
|
1203
|
+
* `theme/graphVars.ts` owns the paint: every React Flow surface is set through
|
|
1204
|
+
* its own `--xy-*` variable there, never through a rule.
|
|
1205
|
+
*
|
|
1206
|
+
* `react-flow/xy.ts` owns the @xyflow DOM contract — it is already the one file
|
|
1207
|
+
* allowed to import the library at all (oxlint `no-restricted-imports`), and a
|
|
1208
|
+
* class NAME is part of that contract the same way an exported symbol is. The
|
|
1209
|
+
* long press has to find the pane to listen on it, which is a DOM lookup and
|
|
1210
|
+
* not a paint rule; putting the string at the call site would mean a library
|
|
1211
|
+
* rename lands in a `querySelector` that returns null in silence.
|
|
1212
|
+
*/
|
|
1213
|
+
const GRAPH_VARS_EXEMPT = ["theme/graphVars.ts", "react-flow/xy.ts"];
|
|
1214
|
+
const REACT_FLOW_SCANNED_RE = /\.(tsx|ts|jsx|js|mjs|cjs|css)$/;
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Offsets that are NOT inside a comment.
|
|
1218
|
+
*
|
|
1219
|
+
* `codeOffsets` masks strings as well, which is the opposite of what this needs:
|
|
1220
|
+
* a hand-written React Flow rule lives in a template literal or a `className`,
|
|
1221
|
+
* so strings are exactly the interesting part. Comments are not — the map's own
|
|
1222
|
+
* allowlist explains itself by naming `.react-flow__node-input`, and that
|
|
1223
|
+
* explanation must not be the thing that fails the build.
|
|
1224
|
+
* @param {string} src
|
|
1225
|
+
*/
|
|
1226
|
+
function nonCommentOffsets(src) {
|
|
1227
|
+
const mask = new Uint8Array(src.length).fill(1);
|
|
1228
|
+
let i = 0;
|
|
1229
|
+
while (i < src.length) {
|
|
1230
|
+
const char = src[i];
|
|
1231
|
+
const next = src[i + 1];
|
|
1232
|
+
if (char === "/" && next === "/") {
|
|
1233
|
+
while (i < src.length && src[i] !== "\n") mask[i++] = 0;
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (char === "/" && next === "*") {
|
|
1237
|
+
while (i < src.length && !(src[i] === "*" && src[i + 1] === "/")) mask[i++] = 0;
|
|
1238
|
+
mask[i] = 0;
|
|
1239
|
+
mask[i + 1] = 0;
|
|
1240
|
+
i += 2;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
i++;
|
|
1244
|
+
}
|
|
1245
|
+
return mask;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* MPO-191: a `.react-flow__` selector under public/<pkg>/src, outside the one
|
|
1250
|
+
* file that owns the mapping.
|
|
1251
|
+
*/
|
|
1252
|
+
function checkGraphVarsCoverage() {
|
|
1253
|
+
/** @type {string[]} */
|
|
1254
|
+
const violations = [];
|
|
1255
|
+
if (!existsSync(PUBLIC_DIR)) return violations;
|
|
1256
|
+
for (const pkg of readdirSync(PUBLIC_DIR, { withFileTypes: true })) {
|
|
1257
|
+
if (!pkg.isDirectory() || pkg.name.startsWith(".") || pkg.name === "node_modules") continue;
|
|
1258
|
+
walkFiles(join(PUBLIC_DIR, pkg.name, "src"), (filePath) => {
|
|
1259
|
+
if (!REACT_FLOW_SCANNED_RE.test(filePath)) return;
|
|
1260
|
+
const posix = filePath.replace(/\\/g, "/");
|
|
1261
|
+
if (GRAPH_VARS_EXEMPT.some((exempt) => posix.endsWith(exempt))) return;
|
|
1262
|
+
// Specs are exempt for the reason check 10 exempts them: a spec that
|
|
1263
|
+
// asserts WHICH classes a variable binds to has to name those classes,
|
|
1264
|
+
// and it ships no paint. Nothing else is exempt, including stories.
|
|
1265
|
+
if (/\.(spec|test)\./.test(filePath)) return;
|
|
1266
|
+
const src = readFileSync(filePath, "utf8");
|
|
1267
|
+
if (!src.includes(".react-flow__")) return;
|
|
1268
|
+
const mask = nonCommentOffsets(src);
|
|
1269
|
+
for (const match of src.matchAll(REACT_FLOW_SELECTOR_RE)) {
|
|
1270
|
+
const index = match.index ?? 0;
|
|
1271
|
+
if (mask[index] !== 1) continue;
|
|
1272
|
+
violations.push(`${relative(ROOT, filePath)}:${lineAt(src, index)} — ${match[0]}`);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
return violations;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1186
1279
|
/**
|
|
1187
1280
|
* Run structural convention checks over a consumer tree.
|
|
1188
1281
|
*
|
|
@@ -1200,6 +1293,7 @@ export function runConventionChecks(options = {}) {
|
|
|
1200
1293
|
const metroFallbacks = checkMetroSubpathFallbacks();
|
|
1201
1294
|
const rnWebExports = checkReactNativeWebExports();
|
|
1202
1295
|
const importMeta = checkImportMetaInPublicSource();
|
|
1296
|
+
const graphVars = checkGraphVarsCoverage();
|
|
1203
1297
|
const tokens = checkTransitionTokens();
|
|
1204
1298
|
const styleTokens = checkStyleTokens();
|
|
1205
1299
|
const themeNames = checkThemeNames();
|
|
@@ -1293,6 +1387,15 @@ export function runConventionChecks(options = {}) {
|
|
|
1293
1387
|
console.error("");
|
|
1294
1388
|
}
|
|
1295
1389
|
|
|
1390
|
+
if (graphVars.length) {
|
|
1391
|
+
failed = true;
|
|
1392
|
+
console.error(
|
|
1393
|
+
"Convention (MPO-191 GRAPH VARS COVERAGE): a `.react-flow__` selector belongs in the token bridge, nowhere else. React Flow reads every paint through `var(--xy-thing, var(--xy-thing-default))`, so the whole surface is settable without touching its stylesheet; a rule written anywhere else wins or loses on specificity instead of on the knob, and the next @xyflow/react upgrade silently changes which. Set the variable in `theme/graphVars.ts`, or put the rule in the package's own `css/` tree (outside `src`) where a reviewer reads it whole. A DOM LOOKUP is the one other legitimate use, and it belongs in `react-flow/xy.ts` beside the rest of the @xyflow contract. A mention inside a comment is fine and is not flagged.\n",
|
|
1394
|
+
);
|
|
1395
|
+
for (const msg of graphVars) console.error(` ${msg}`);
|
|
1396
|
+
console.error("");
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1296
1399
|
if (importMeta.length) {
|
|
1297
1400
|
failed = true;
|
|
1298
1401
|
console.error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/config",
|
|
3
|
-
"version": "7.7.
|
|
3
|
+
"version": "7.7.3",
|
|
4
4
|
"description": "Shared build and test configuration presets for multiplatform.one",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"config",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"vite-plugin-external": "^6.2.2",
|
|
103
103
|
"vite-plugin-i18next-loader": "^3.1.3",
|
|
104
104
|
"vitest": "^4.1.5",
|
|
105
|
-
"@multiplatform.one/utils": "7.7.
|
|
105
|
+
"@multiplatform.one/utils": "7.7.3"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"tsdown": "^0.21.10",
|