basalt-ui 1.22.0 → 1.23.1
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/AGENTS.md +29 -12
- package/CHANGELOG.md +28 -0
- package/MIGRATING.md +207 -12
- package/README.md +74 -21
- package/agent/rules/basalt-app.md +33 -3
- package/agent/rules/basalt-charts.md +78 -7
- package/agent/rules/basalt-content.md +3 -3
- package/agent/rules/basalt-mantine.md +4 -4
- package/agent/rules/basalt-tokens.md +35 -6
- package/agent/skills/basalt-app/SKILL.md +25 -12
- package/agent/skills/basalt-charts/SKILL.md +27 -10
- package/agent/templates/CLAUDE-block.md.tpl +3 -2
- package/agent/templates/DESIGN.md.tpl +3 -1
- package/configs/oxlint-plugin.js +49 -8
- package/configs/oxlint-plugin.test.ts +88 -31
- package/dist/charts/hooks/useBandPlot.d.ts +138 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -0
- package/dist/charts/hooks/useBandPlot.js +127 -0
- package/dist/charts/index.d.ts +4 -0
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +14 -0
- package/dist/charts/kinds/BandStrip.d.ts +127 -0
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -0
- package/dist/charts/kinds/BandStrip.js +271 -0
- package/dist/charts/kinds/BandStrip.test.d.ts +2 -0
- package/dist/charts/kinds/BandStrip.test.d.ts.map +1 -0
- package/dist/charts/kinds/Bars.d.ts +5 -1
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +2 -0
- package/dist/charts/kinds/MirroredBars.d.ts +95 -0
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -0
- package/dist/charts/kinds/MirroredBars.js +328 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts +2 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts.map +1 -0
- package/dist/charts/kinds/MultiLine.d.ts +5 -1
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +2 -0
- package/dist/charts/kinds/StackedArea.d.ts +5 -1
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +2 -0
- package/dist/charts/kinds/ZonedLine.d.ts +5 -1
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +2 -0
- package/dist/charts/primitives/CartesianChart.d.ts +18 -2
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +5 -2
- package/dist/charts/primitives/HatchPattern.d.ts +31 -0
- package/dist/charts/primitives/HatchPattern.d.ts.map +1 -0
- package/dist/charts/primitives/HatchPattern.js +31 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +228 -31
- package/dist/cli/shipped-versions.test.d.ts +2 -0
- package/dist/cli/shipped-versions.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +60 -13
- package/dist/vite.d.ts +42 -3
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +67 -30
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/hooks/useBandPlot.ts +302 -0
- package/src/charts/index.ts +14 -0
- package/src/charts/kinds/BandStrip.test.tsx +282 -0
- package/src/charts/kinds/BandStrip.tsx +457 -0
- package/src/charts/kinds/Bars.tsx +7 -1
- package/src/charts/kinds/DualPanel.test.tsx +10 -4
- package/src/charts/kinds/MirroredBars.test.tsx +232 -0
- package/src/charts/kinds/MirroredBars.tsx +491 -0
- package/src/charts/kinds/MultiLine.tsx +7 -1
- package/src/charts/kinds/StackedArea.tsx +7 -1
- package/src/charts/kinds/ZonedLine.tsx +7 -1
- package/src/charts/primitives/CartesianChart.test.tsx +86 -4
- package/src/charts/primitives/CartesianChart.tsx +28 -3
- package/src/charts/primitives/HatchPattern.tsx +55 -0
- package/src/cli/help.test.ts +74 -1
- package/src/cli/index.ts +436 -52
- package/src/cli/shipped-versions.test.ts +83 -0
- package/src/cli/tokens-css.test.ts +62 -0
- package/src/cli/toolchain-wiring.test.ts +171 -4
- package/src/cli/waiver-audit.test.ts +7 -3
- package/src/guard/check-source.test.ts +211 -42
- package/src/guard/index.ts +149 -23
- package/src/vite.test.ts +79 -0
- package/src/vite.ts +141 -39
package/dist/cli/index.js
CHANGED
|
@@ -160,6 +160,16 @@ function hasBasaltProject(dir) {
|
|
|
160
160
|
return false;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
function descendantProjects(cwd, depth = 2) {
|
|
164
|
+
if (depth === 0) return [];
|
|
165
|
+
const out = [];
|
|
166
|
+
for (const name of subdirNames(cwd, { skipDotDirs: true })) {
|
|
167
|
+
const dir = resolve(cwd, name);
|
|
168
|
+
if (hasBasaltProject(dir)) out.push(dir);
|
|
169
|
+
else out.push(...descendantProjects(dir, depth - 1));
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
163
173
|
function resolveProjectDir(cwd) {
|
|
164
174
|
const override = process.env["BASALT_CWD"];
|
|
165
175
|
if (override !== void 0 && override.length > 0) {
|
|
@@ -167,7 +177,8 @@ function resolveProjectDir(cwd) {
|
|
|
167
177
|
return { dir, relocatedFrom: dir === cwd ? null : cwd, ambiguous: null };
|
|
168
178
|
}
|
|
169
179
|
if (hasBasaltProject(cwd)) return { dir: cwd, relocatedFrom: null, ambiguous: null };
|
|
170
|
-
const
|
|
180
|
+
const declared = collectWorkspacePackages(cwd).packages.filter((pkg) => pkg.dir !== cwd && hasBasaltProject(pkg.dir)).map((pkg) => pkg.dir);
|
|
181
|
+
const candidates = declared.length > 0 ? declared : descendantProjects(cwd);
|
|
171
182
|
if (candidates.length === 1) {
|
|
172
183
|
return { dir: candidates[0], relocatedFrom: cwd, ambiguous: null };
|
|
173
184
|
}
|
|
@@ -239,7 +250,7 @@ function checkTheme(invocationCwd = process.cwd(), flags = []) {
|
|
|
239
250
|
const project = resolveProjectDir(invocationCwd);
|
|
240
251
|
if (project.ambiguous !== null) {
|
|
241
252
|
console.error(
|
|
242
|
-
`\u2716 basalt-ui check-theme: no basalt config at ${invocationCwd}, and ${project.ambiguous.length}
|
|
253
|
+
`\u2716 basalt-ui check-theme: no basalt config at ${invocationCwd}, and ${project.ambiguous.length} packages below it carry one (${project.ambiguous.map((d) => relativePosix(invocationCwd, d)).join(", ")}) \u2014 run it from one of them, or set BASALT_CWD to pick.`
|
|
243
254
|
);
|
|
244
255
|
return 1;
|
|
245
256
|
}
|
|
@@ -565,8 +576,9 @@ basalt.exemptRules entries (${exemptCount}):`);
|
|
|
565
576
|
`
|
|
566
577
|
${live} live, ${dead} dead, ${outOfReach} unjudgeable, ${unaccountable} unaccountable (reported as theme-allow-unscoped by a normal run).`
|
|
567
578
|
);
|
|
579
|
+
const includes = cfg.include ?? [];
|
|
568
580
|
lines.push(
|
|
569
|
-
`Scope: the ${scanned.length} file(s) check-theme scans under basalt.roots (${resolveRoots(cfg).join(", ")}). A waiver
|
|
581
|
+
`Scope: the ${scanned.length} file(s) check-theme scans \u2014 everything under basalt.roots (${resolveRoots(cfg).join(", ")}), plus each root's sibling index.html and public/ tree, ` + (includes.length === 0 ? "plus anything named in basalt.include (none). " : `plus basalt.include (${includes.join(", ")}). `) + "A waiver in a file none of those reach is not audited \u2014 widen roots, or name it in basalt.include, or accept that nothing polices that file."
|
|
570
582
|
);
|
|
571
583
|
if (dead > 0) {
|
|
572
584
|
lines.push(
|
|
@@ -605,9 +617,13 @@ function readFrameworkVersion(pkgRoot) {
|
|
|
605
617
|
return "0.0.0";
|
|
606
618
|
}
|
|
607
619
|
}
|
|
620
|
+
function basaltBinCommand(install, cwd) {
|
|
621
|
+
if (install.dir === null) return "bunx basalt-ui";
|
|
622
|
+
const binDir = resolve(install.dir, "..", ".bin");
|
|
623
|
+
return `${relativePosix(cwd, binDir).replace(/^\.\//, "")}/basalt-ui`;
|
|
624
|
+
}
|
|
608
625
|
function buildTemplateVars(pkgRoot, cwd, cfg) {
|
|
609
626
|
const install = findBasaltInstall(cwd);
|
|
610
|
-
const binDir = install.dir === null ? null : resolve(install.dir, "..", ".bin");
|
|
611
627
|
return {
|
|
612
628
|
APP_NAME: readPackageName(cwd),
|
|
613
629
|
BASALT_VERSION: readFrameworkVersion(pkgRoot),
|
|
@@ -616,7 +632,7 @@ function buildTemplateVars(pkgRoot, cwd, cfg) {
|
|
|
616
632
|
ROOTS_GLOBS: resolveRoots(cfg).map(toRootGlob).join(" "),
|
|
617
633
|
OXLINT_PRESET_PATH: shippedAssetPath(install, cwd, "configs/oxlint.json"),
|
|
618
634
|
LEFTHOOK_PRESET_PATH: shippedAssetPath(install, cwd, "configs/lefthook.yml"),
|
|
619
|
-
BASALT_BIN:
|
|
635
|
+
BASALT_BIN: basaltBinCommand(install, cwd)
|
|
620
636
|
};
|
|
621
637
|
}
|
|
622
638
|
function fillTemplate(tpl, vars) {
|
|
@@ -860,6 +876,16 @@ function migrateLegacyOxfmt(cwd, pkgRoot, manifest) {
|
|
|
860
876
|
const shipped = readSource(pkgRoot, "configs/oxfmt.json");
|
|
861
877
|
if (shipped !== null && onDisk === shipped) unlinkSync(legacyAbs);
|
|
862
878
|
}
|
|
879
|
+
const DESIGN_VERSION_LINE = /^(> Managed by basalt-ui) \(\d+\.\d+\.\d+\)\./m;
|
|
880
|
+
function migrateDesignVersionLine(cwd) {
|
|
881
|
+
const dest = resolve(cwd, "DESIGN.md");
|
|
882
|
+
const onDisk = readIfExists(dest);
|
|
883
|
+
if (onDisk === null || !DESIGN_VERSION_LINE.test(onDisk)) return;
|
|
884
|
+
writeFileEnsuringDir(
|
|
885
|
+
dest,
|
|
886
|
+
onDisk.replace(DESIGN_VERSION_LINE, "$1 \u2014 run `basalt-ui doctor` for the version.")
|
|
887
|
+
);
|
|
888
|
+
}
|
|
863
889
|
function writeFileEnsuringDir(abs, content) {
|
|
864
890
|
mkdirSync(dirname(abs), { recursive: true });
|
|
865
891
|
writeFileSync(abs, content);
|
|
@@ -1246,7 +1272,7 @@ Theme guard hook: add to .claude/settings.json \u2192 hooks.PreToolUse to catch
|
|
|
1246
1272
|
"PreToolUse": [
|
|
1247
1273
|
{
|
|
1248
1274
|
"matcher": "Write|Edit|MultiEdit",
|
|
1249
|
-
"hooks": [{ "type": "command", "command": "
|
|
1275
|
+
"hooks": [{ "type": "command", "command": "${ctx.vars.BASALT_BIN} guard-hook" }]
|
|
1250
1276
|
}
|
|
1251
1277
|
]
|
|
1252
1278
|
}`
|
|
@@ -1277,6 +1303,9 @@ function readOxlintPresetPlugins(pkgRoot) {
|
|
|
1277
1303
|
return [];
|
|
1278
1304
|
}
|
|
1279
1305
|
}
|
|
1306
|
+
function parentInstallAdvice(cwd, parent, verb) {
|
|
1307
|
+
return `This repo's install is at ${relativePosix(cwd, parent)} (${parent}) \u2014 run ${verb} there, or set BASALT_CWD to it.`;
|
|
1308
|
+
}
|
|
1280
1309
|
function findManifestAbove(dir) {
|
|
1281
1310
|
let current = resolve(dir, "..");
|
|
1282
1311
|
for (; ; ) {
|
|
@@ -1287,10 +1316,15 @@ function findManifestAbove(dir) {
|
|
|
1287
1316
|
}
|
|
1288
1317
|
}
|
|
1289
1318
|
function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
1319
|
+
const syncFlags = opts.flags ?? [];
|
|
1320
|
+
if (conflictingProfileFlags(syncFlags)) {
|
|
1321
|
+
console.error("basalt-ui sync: --tokens-only and --framework are alternatives \u2014 pass one.");
|
|
1322
|
+
return 1;
|
|
1323
|
+
}
|
|
1290
1324
|
const project = resolveProjectDir(invocationCwd);
|
|
1291
1325
|
if (project.ambiguous !== null) {
|
|
1292
1326
|
console.error(
|
|
1293
|
-
`\u2716 basalt-ui sync: no basalt config at ${invocationCwd}, and ${project.ambiguous.length}
|
|
1327
|
+
`\u2716 basalt-ui sync: no basalt config at ${invocationCwd}, and ${project.ambiguous.length} packages below it carry one (${project.ambiguous.map((d) => relativePosix(invocationCwd, d)).join(", ")}) \u2014 run it from one of them, or set BASALT_CWD to pick.`
|
|
1294
1328
|
);
|
|
1295
1329
|
return 1;
|
|
1296
1330
|
}
|
|
@@ -1300,18 +1334,27 @@ function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
|
1300
1334
|
`basalt-ui sync: no basalt config at ${project.relocatedFrom} \u2014 running in ${relativePosix(project.relocatedFrom, cwd)}, where it lives.`
|
|
1301
1335
|
);
|
|
1302
1336
|
}
|
|
1337
|
+
if (declaredProfile(readBasaltConfig(cwd), syncFlags) === "tokens-only") {
|
|
1338
|
+
console.log(
|
|
1339
|
+
`basalt-ui sync: ${MANIFEST_PATH}: n/a \u2014 a tokens-only consumer has no scaffold to reconcile. The token layer is refreshed by re-running \`basalt-ui tokens:css\`, and \`tokens:css --check\` is its drift gate. Pass --framework to force the full profile.`
|
|
1340
|
+
);
|
|
1341
|
+
return 0;
|
|
1342
|
+
}
|
|
1303
1343
|
if (!existsSync(resolve(cwd, MANIFEST_PATH))) {
|
|
1304
1344
|
const elsewhere = findManifestAbove(cwd);
|
|
1305
1345
|
const wouldWrite = managedFiles(resolvePeerFlags(cwd, {}), resolvePlacement(cwd)).length;
|
|
1306
1346
|
console.error(
|
|
1307
|
-
`\u2716 basalt-ui sync: no ${MANIFEST_PATH} at ${cwd} \u2014 refusing to scaffold. \`sync\` refreshes an EXISTING basalt install; creating one is \`basalt-ui init\`'s decision, and continuing here would write ${wouldWrite} managed file(s) into this directory.` + (elsewhere === null ? " No manifest exists at any ancestor either \u2014 run `basalt-ui init` here if this package really is meant to be a consumer." : `
|
|
1347
|
+
`\u2716 basalt-ui sync: no ${MANIFEST_PATH} at ${cwd} \u2014 refusing to scaffold. \`sync\` refreshes an EXISTING basalt install; creating one is \`basalt-ui init\`'s decision, and continuing here would write ${wouldWrite} managed file(s) into this directory.` + (elsewhere === null ? " No manifest exists at any ancestor either \u2014 run `basalt-ui init` here if this package really is meant to be a consumer." : ` ${parentInstallAdvice(cwd, elsewhere, "sync")}`)
|
|
1308
1348
|
);
|
|
1309
1349
|
return 1;
|
|
1310
1350
|
}
|
|
1311
1351
|
const rootsState = reconcileRoots(cwd, { write: opts.check !== true });
|
|
1312
1352
|
const ctx = renderContext(cwd);
|
|
1313
1353
|
const manifest = readManifest(cwd);
|
|
1314
|
-
if (!opts.check)
|
|
1354
|
+
if (!opts.check) {
|
|
1355
|
+
migrateLegacyOxfmt(cwd, ctx.pkgRoot, manifest);
|
|
1356
|
+
migrateDesignVersionLine(cwd);
|
|
1357
|
+
}
|
|
1315
1358
|
const peers = resolvePeerFlags(cwd, {});
|
|
1316
1359
|
const placement = resolvePlacement(cwd);
|
|
1317
1360
|
const files = managedFiles(peers, placement);
|
|
@@ -1634,7 +1677,7 @@ function doctor(invocationCwd = process.cwd(), flags = []) {
|
|
|
1634
1677
|
`
|
|
1635
1678
|
basalt-ui doctor \u2014 ${invocationCwd}
|
|
1636
1679
|
|
|
1637
|
-
\u2716 no basalt config at ${invocationCwd}, and ${project.ambiguous.length}
|
|
1680
|
+
\u2716 no basalt config at ${invocationCwd}, and ${project.ambiguous.length} packages below it carry one (${project.ambiguous.map((d) => relativePosix(invocationCwd, d)).join(", ")}) \u2014 run doctor from one of them, or set BASALT_CWD to pick.
|
|
1638
1681
|
|
|
1639
1682
|
1 hard failure(s), 0 warning(s).
|
|
1640
1683
|
`
|
|
@@ -1670,6 +1713,7 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1670
1713
|
}
|
|
1671
1714
|
const manifestAbs = resolve(cwd, MANIFEST_PATH);
|
|
1672
1715
|
const manifestExists = existsSync(manifestAbs);
|
|
1716
|
+
const parentInstall = manifestExists ? null : findManifestAbove(cwd);
|
|
1673
1717
|
const profile = inferredProfile(cwd, cfg, flags);
|
|
1674
1718
|
if (profile === "tokens-only") {
|
|
1675
1719
|
lines.push(
|
|
@@ -1685,6 +1729,10 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1685
1729
|
pass(`${MANIFEST_PATH}: n/a \u2014 a tokens-only consumer has no scaffold to reconcile`);
|
|
1686
1730
|
} else if (manifestExists) {
|
|
1687
1731
|
pass(`${MANIFEST_PATH} exists`);
|
|
1732
|
+
} else if (parentInstall !== null) {
|
|
1733
|
+
fail(
|
|
1734
|
+
`${MANIFEST_PATH} missing here, but this is not an unscaffolded consumer. ${parentInstallAdvice(cwd, parentInstall, "doctor")} \`basalt-ui init\` is NOT the fix: it would scaffold a SECOND consumer beside the real one.`
|
|
1735
|
+
);
|
|
1688
1736
|
} else {
|
|
1689
1737
|
fail(`${MANIFEST_PATH} missing \u2014 run \`basalt-ui init\` to scaffold the consumer repo`);
|
|
1690
1738
|
}
|
|
@@ -1780,7 +1828,7 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1780
1828
|
const oxlintrcRaw = readIfExists(resolve(cwd, ".oxlintrc.json"));
|
|
1781
1829
|
if (oxlintrcRaw === null) {
|
|
1782
1830
|
fail(
|
|
1783
|
-
".oxlintrc.json missing \u2014 the shipped oxlint preset (the basalt/* design rules) is not active. Run `basalt-ui init` to seed it."
|
|
1831
|
+
".oxlintrc.json missing \u2014 the shipped oxlint preset (the basalt/* design rules) is not active. " + (parentInstall === null ? "Run `basalt-ui init` to seed it." : `${parentInstallAdvice(cwd, parentInstall, "doctor")} The preset is seeded once, at the install \u2014 not per package.`)
|
|
1784
1832
|
);
|
|
1785
1833
|
} else {
|
|
1786
1834
|
const parsed = parseJsonc(oxlintrcRaw);
|
|
@@ -1828,30 +1876,43 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1828
1876
|
);
|
|
1829
1877
|
} else if (lefthookGate.kind === "absent") {
|
|
1830
1878
|
warn(
|
|
1831
|
-
`${lefthookGate.file}: \`lefthook dump\` resolves the merged config and NO pre-commit command runs check-theme \u2014 the theme guard is not gating commits. Either add "extends: [${lefthookCorrect}]", or add your own command running
|
|
1879
|
+
`${lefthookGate.file}: \`lefthook dump\` resolves the merged config and NO pre-commit command runs check-theme \u2014 the theme guard is not gating commits. Either add "extends: [${lefthookCorrect}]", or add your own command running \`${basaltBinCommand(install, cwd)} check-theme\` (with \`root:\` if it must run in a subdirectory).`
|
|
1832
1880
|
);
|
|
1833
1881
|
} else {
|
|
1834
1882
|
warn(
|
|
1835
|
-
`${lefthookGate.file} names no basalt gate and \`lefthook dump\` could not be run here, so this check cannot tell a missing gate from one wired through an \`include:\` or a remote config it never read \u2014 treat it as advisory. If the guard really is unwired, add "extends: [${lefthookCorrect}]" or your own command running
|
|
1883
|
+
`${lefthookGate.file} names no basalt gate and \`lefthook dump\` could not be run here, so this check cannot tell a missing gate from one wired through an \`include:\` or a remote config it never read \u2014 treat it as advisory. If the guard really is unwired, add "extends: [${lefthookCorrect}]" or your own command running \`${basaltBinCommand(install, cwd)} check-theme\`.`
|
|
1836
1884
|
);
|
|
1837
1885
|
}
|
|
1838
1886
|
const publicDir = resolve(cwd, "public");
|
|
1839
1887
|
if (profile === "framework" && existsSync(publicDir)) {
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
"
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
"web-app-manifest-512x512.png"
|
|
1847
|
-
];
|
|
1848
|
-
const missingIcons = iconFiles.filter((f) => !existsSync(resolve(publicDir, f)));
|
|
1849
|
-
if (missingIcons.length > 0) {
|
|
1850
|
-
warn(
|
|
1851
|
-
`public/ is missing basaltAppPlugin icon file(s): ${missingIcons.join(", ")} \u2014 generate them (e.g. via realfavicongenerator.net) and place them at the public/ root.`
|
|
1888
|
+
const icons = readAppIconsOption(cwd);
|
|
1889
|
+
if (icons.kind === "none") {
|
|
1890
|
+
pass("basaltAppPlugin is configured with no icons \u2014 nothing to check");
|
|
1891
|
+
} else if (icons.kind === "explicit") {
|
|
1892
|
+
const missing = icons.sources.filter(
|
|
1893
|
+
(src) => !existsSync(resolve(publicDir, src.replace(/^\//, "")))
|
|
1852
1894
|
);
|
|
1895
|
+
if (missing.length > 0) {
|
|
1896
|
+
warn(
|
|
1897
|
+
`basaltAppPlugin declares icon(s) that are not in public/: ${missing.join(", ")} \u2014 the manifest and the head links will point at a 404. Place them, or drop them from the \`icons\` array.`
|
|
1898
|
+
);
|
|
1899
|
+
} else {
|
|
1900
|
+
pass(
|
|
1901
|
+
`public/ has all ${icons.sources.length} icon file(s) basaltAppPlugin's \`icons\` option names`
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1853
1904
|
} else {
|
|
1854
|
-
|
|
1905
|
+
const dir = icons.kind === "default" ? icons.dir : null;
|
|
1906
|
+
const iconRoot = dir === null ? publicDir : resolve(publicDir, dir.replace(/^\//, ""));
|
|
1907
|
+
const shown = dir === null ? "public/" : `public/${dir.replace(/^\//, "")}`;
|
|
1908
|
+
const missingIcons = DEFAULT_APP_ICON_FILES.filter((f) => !existsSync(resolve(iconRoot, f)));
|
|
1909
|
+
if (missingIcons.length > 0) {
|
|
1910
|
+
warn(
|
|
1911
|
+
`${shown} is missing basaltAppPlugin icon file(s): ${missingIcons.join(", ")} \u2014 generate them (e.g. via realfavicongenerator.net) and place them at the ${shown} root, or name the icons this app really has via the plugin's \`icons\` array.`
|
|
1912
|
+
);
|
|
1913
|
+
} else {
|
|
1914
|
+
pass(`${shown} has all of basaltAppPlugin's default icon files`);
|
|
1915
|
+
}
|
|
1855
1916
|
}
|
|
1856
1917
|
}
|
|
1857
1918
|
const workspaceWalk = collectWorkspacePackages(cwd);
|
|
@@ -1972,6 +2033,68 @@ basalt-ui v${pkgVersion} \u2014 published surface
|
|
|
1972
2033
|
console.log("");
|
|
1973
2034
|
return 0;
|
|
1974
2035
|
}
|
|
2036
|
+
const DEFAULT_APP_ICON_FILES = [
|
|
2037
|
+
"favicon.ico",
|
|
2038
|
+
"favicon.svg",
|
|
2039
|
+
"favicon-96x96.png",
|
|
2040
|
+
"apple-touch-icon.png",
|
|
2041
|
+
"web-app-manifest-192x192.png",
|
|
2042
|
+
"web-app-manifest-512x512.png"
|
|
2043
|
+
];
|
|
2044
|
+
const VITE_CONFIG_FILES = [
|
|
2045
|
+
"vite.config.ts",
|
|
2046
|
+
"vite.config.mts",
|
|
2047
|
+
"vite.config.cts",
|
|
2048
|
+
"vite.config.js",
|
|
2049
|
+
"vite.config.mjs",
|
|
2050
|
+
"vite.config.cjs"
|
|
2051
|
+
];
|
|
2052
|
+
function balancedSpan(text, open) {
|
|
2053
|
+
const pairs = { "(": ")", "{": "}", "[": "]" };
|
|
2054
|
+
const closer = pairs[text[open]];
|
|
2055
|
+
if (closer === void 0) return null;
|
|
2056
|
+
let depth = 0;
|
|
2057
|
+
for (let i = open; i < text.length; i++) {
|
|
2058
|
+
const ch = text[i];
|
|
2059
|
+
if (ch === text[open]) depth++;
|
|
2060
|
+
else if (ch === closer) {
|
|
2061
|
+
depth--;
|
|
2062
|
+
if (depth === 0) return text.slice(open, i + 1);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
function readAppIconsOption(cwd) {
|
|
2068
|
+
const configPath = VITE_CONFIG_FILES.map((f) => resolve(cwd, f)).find((f) => existsSync(f));
|
|
2069
|
+
if (configPath === void 0) return { kind: "unknown" };
|
|
2070
|
+
const text = readIfExists(configPath);
|
|
2071
|
+
if (text === null) return { kind: "unknown" };
|
|
2072
|
+
const call = text.indexOf("basaltAppPlugin(");
|
|
2073
|
+
if (call === -1) return { kind: "unknown" };
|
|
2074
|
+
const args = balancedSpan(text, call + "basaltAppPlugin".length);
|
|
2075
|
+
if (args === null) return { kind: "unknown" };
|
|
2076
|
+
const key = args.search(/\bicons\s*:/);
|
|
2077
|
+
if (key === -1) return { kind: "default", dir: null };
|
|
2078
|
+
const valueStart = args.indexOf(":", key) + 1;
|
|
2079
|
+
const rest = args.slice(valueStart).trimStart();
|
|
2080
|
+
if (rest.startsWith("false")) return { kind: "none" };
|
|
2081
|
+
const offset = args.length - rest.length;
|
|
2082
|
+
if (rest.startsWith("[")) {
|
|
2083
|
+
const list = balancedSpan(args, offset);
|
|
2084
|
+
if (list === null) return { kind: "unknown" };
|
|
2085
|
+
const sources = [...list.matchAll(/\bsrc\s*:\s*['"`]([^'"`]+)['"`]/g)].map(
|
|
2086
|
+
(m) => m[1]
|
|
2087
|
+
);
|
|
2088
|
+
return sources.length === 0 ? { kind: "none" } : { kind: "explicit", sources };
|
|
2089
|
+
}
|
|
2090
|
+
if (rest.startsWith("{")) {
|
|
2091
|
+
const obj = balancedSpan(args, offset);
|
|
2092
|
+
if (obj === null) return { kind: "unknown" };
|
|
2093
|
+
const dir = /\bdir\s*:\s*['"`]([^'"`]+)['"`]/.exec(obj);
|
|
2094
|
+
return { kind: "default", dir: dir === null ? null : dir[1] };
|
|
2095
|
+
}
|
|
2096
|
+
return { kind: "unknown" };
|
|
2097
|
+
}
|
|
1975
2098
|
function flagValue(flags, name) {
|
|
1976
2099
|
const i = flags.indexOf(name);
|
|
1977
2100
|
if (i === -1) return void 0;
|
|
@@ -1985,6 +2108,12 @@ function generatedHeader(version, command, flags) {
|
|
|
1985
2108
|
/* basalt-ui ${version} \u2014 \`basalt-ui ${invocation}\` */
|
|
1986
2109
|
`;
|
|
1987
2110
|
}
|
|
2111
|
+
function withoutProvenanceLine(css) {
|
|
2112
|
+
const lines = css.split("\n");
|
|
2113
|
+
if (lines.length < 2) return css;
|
|
2114
|
+
lines[1] = "";
|
|
2115
|
+
return lines.join("\n");
|
|
2116
|
+
}
|
|
1988
2117
|
function normalizeColorFunctions(css) {
|
|
1989
2118
|
return css.replace(/\b(rgba?|hsla?)\(([^()]*)\)/g, (_match, fn, args) => {
|
|
1990
2119
|
const parts = args.split(",").map((part) => normalizeCssNumber(part.trim()));
|
|
@@ -2019,12 +2148,15 @@ function emitGeneratedCss(content, flags, cwd, command) {
|
|
|
2019
2148
|
);
|
|
2020
2149
|
return 1;
|
|
2021
2150
|
}
|
|
2022
|
-
if (onDisk === content) {
|
|
2023
|
-
|
|
2151
|
+
if (withoutProvenanceLine(onDisk) === withoutProvenanceLine(content)) {
|
|
2152
|
+
const staleProvenance = onDisk.split("\n")[1] !== content.split("\n")[1];
|
|
2153
|
+
console.log(
|
|
2154
|
+
`\u2713 ${command} --check: ${shown} is up to date.` + (staleProvenance ? " (Its provenance line still names an older basalt-ui; that line is deliberately not gated \u2014 re-run without --check whenever you want it refreshed.)" : "")
|
|
2155
|
+
);
|
|
2024
2156
|
return 0;
|
|
2025
2157
|
}
|
|
2026
2158
|
console.error(
|
|
2027
|
-
`\u2716 ${command} --check: ${shown} differs from what \`basalt-ui ${command}\` emits today (on disk ${onDisk.split("\n").length} lines, emitted ${content.split("\n").length}) \u2014 re-run the same command without --check and commit the result.`
|
|
2159
|
+
`\u2716 ${command} --check: ${shown} differs from what \`basalt-ui ${command}\` emits today (on disk ${onDisk.split("\n").length} lines, emitted ${content.split("\n").length}) \u2014 re-run the same command without --check and commit the result. The \`@generated\` header's provenance line is excluded from this comparison, so a version bump alone never trips it.`
|
|
2028
2160
|
);
|
|
2029
2161
|
return 1;
|
|
2030
2162
|
}
|
|
@@ -2205,13 +2337,71 @@ async function guardHook(cwd = process.cwd()) {
|
|
|
2205
2337
|
}
|
|
2206
2338
|
return 0;
|
|
2207
2339
|
}
|
|
2208
|
-
const
|
|
2340
|
+
const COMMAND_FLAGS = {
|
|
2341
|
+
init: ["--with-router", "--with-query", "--merge-lint"],
|
|
2342
|
+
sync: ["--force", "--check", "--tokens-only", "--framework"],
|
|
2343
|
+
"check-theme": ["--audit-allows", "--tokens-only", "--framework"],
|
|
2344
|
+
"check-coverage": [],
|
|
2345
|
+
info: ["--json"],
|
|
2346
|
+
doctor: ["--tokens-only", "--framework"],
|
|
2347
|
+
"guard-hook": [],
|
|
2348
|
+
"tokens:css": [
|
|
2349
|
+
"--out",
|
|
2350
|
+
"--check",
|
|
2351
|
+
"--selector-attribute",
|
|
2352
|
+
"--selector-class",
|
|
2353
|
+
"--light-class",
|
|
2354
|
+
"--dark-value",
|
|
2355
|
+
"--light-value",
|
|
2356
|
+
"--default-scheme",
|
|
2357
|
+
"--media-fallback",
|
|
2358
|
+
"--only",
|
|
2359
|
+
"--no-legacy-aliases"
|
|
2360
|
+
],
|
|
2361
|
+
"fonts:css": ["--out", "--check"]
|
|
2362
|
+
};
|
|
2363
|
+
const VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
2364
|
+
"--out",
|
|
2365
|
+
"--selector-attribute",
|
|
2366
|
+
"--selector-class",
|
|
2367
|
+
"--light-class",
|
|
2368
|
+
"--dark-value",
|
|
2369
|
+
"--light-value",
|
|
2370
|
+
"--default-scheme",
|
|
2371
|
+
"--only"
|
|
2372
|
+
]);
|
|
2373
|
+
function unknownFlag(cmd, flags) {
|
|
2374
|
+
const allowed = COMMAND_FLAGS[cmd];
|
|
2375
|
+
if (allowed === void 0) return null;
|
|
2376
|
+
for (let i = 0; i < flags.length; i++) {
|
|
2377
|
+
const flag = flags[i];
|
|
2378
|
+
if (!flag.startsWith("-")) continue;
|
|
2379
|
+
if (!allowed.includes(flag)) return flag;
|
|
2380
|
+
if (VALUE_FLAGS.has(flag)) i++;
|
|
2381
|
+
}
|
|
2382
|
+
return null;
|
|
2383
|
+
}
|
|
2384
|
+
const USAGE = "Usage: basalt-ui <--version | init [--with-router] [--with-query] [--merge-lint] |\n sync [--force] [--check] [--tokens-only|--framework] |\n check-theme [--audit-allows] | check-coverage | info [--json] |\n doctor [--tokens-only|--framework] | guard-hook | tokens:css | fonts:css | help>\n\ncheck-theme [--tokens-only|--framework] [--audit-allows]\n --audit-allows reports instead of scanning: every `theme-allow` annotation and every\n `basalt.exemptRules` entry, with what each one still suppresses \u2014 proved by re-running the\n guard with that one waiver neutralized, not by reading its text. Exits 1 on a waiver that\n suppresses nothing (dead), which is what makes it usable as a CI gate.\n\ntokens:css [--out <path>] [--check] [--selector-attribute <attr> | --selector-class <class>]\n [--light-class <class>] [--dark-value <v>] [--light-value <v>]\n [--default-scheme <dark|light|none>] [--media-fallback] [--only <core|all>]\n [--no-legacy-aliases]\n Emit the --vx-* stylesheet (stdout unless --out). Defaults reproduce basalt-ui/tokens.css.\n --selector-class emits `:root.dark` instead of an attribute selector (the Tailwind convention).\n --check writes nothing and exits 1 when --out differs from what would be emitted (a CI gate).\n --no-legacy-aliases drops the deprecated camelCase spellings (--vx-accentFill and friends),\n which are emitted by default as aliases of the canonical kebab-case names.\n\nfonts:css [--out <path>] [--check]\n Emit the shipped --basalt-font-* stacks as plain CSS \u2014 the typeface half of the token layer,\n otherwise reachable only by importing styles.css.\n\ndoctor [--tokens-only|--framework]\n Auto-detects a tokens-only consumer (no manifest + no @mantine/core) and checks only what\n applies. A check that cannot RUN is reported as SKIPPED and exits non-zero.\n\nsync [--force] [--check] [--tokens-only|--framework]\n Refreshes an EXISTING install; it never creates one (that is init). A tokens-only consumer\n has no scaffold to reconcile, so sync reports n/a and exits 0 there \u2014 as doctor does.\n\ncheck-theme / doctor / sync all resolve their project the same way: they honour BASALT_CWD, and\nrelocate to the single package below the cwd carrying a basalt config when invoked from a repo\nroot that has none \u2014 several carrying one is ambiguous and exits 1 in ALL THREE.\n--tokens-only and --framework are alternatives; passing both is an error.\n\nEvery subcommand accepts --help / -h to print this message and exit without running.\n--version / -v prints the resolved package version and exits 0 \u2014 the one command that proves\nWHICH basalt-ui ran, which a `bunx` cache can otherwise make a lie. An unknown command or an\nunrecognized flag exits 1 and names what it did not understand; neither is ever ignored.";
|
|
2209
2385
|
function run(argv, cwd = process.cwd()) {
|
|
2210
2386
|
const [cmd, ...flags] = argv;
|
|
2211
2387
|
if (cmd === "help" || cmd === "--help" || cmd === "-h" || flags.includes("--help") || flags.includes("-h")) {
|
|
2212
2388
|
console.log(USAGE);
|
|
2213
2389
|
return 0;
|
|
2214
2390
|
}
|
|
2391
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
2392
|
+
console.log(readFrameworkVersion(packageRoot()));
|
|
2393
|
+
return 0;
|
|
2394
|
+
}
|
|
2395
|
+
if (cmd !== void 0) {
|
|
2396
|
+
const bad = unknownFlag(cmd, flags);
|
|
2397
|
+
if (bad !== null) {
|
|
2398
|
+
console.error(
|
|
2399
|
+
`basalt-ui ${cmd}: unrecognized flag '${bad}'. Accepted: ${(COMMAND_FLAGS[cmd] ?? []).join(" ") || "(none)"} --help
|
|
2400
|
+
`
|
|
2401
|
+
);
|
|
2402
|
+
return 1;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2215
2405
|
switch (cmd) {
|
|
2216
2406
|
case "init":
|
|
2217
2407
|
return init(cwd, {
|
|
@@ -2220,7 +2410,10 @@ function run(argv, cwd = process.cwd()) {
|
|
|
2220
2410
|
mergeLint: flags.includes("--merge-lint")
|
|
2221
2411
|
});
|
|
2222
2412
|
case "sync":
|
|
2223
|
-
return sync(
|
|
2413
|
+
return sync(
|
|
2414
|
+
{ force: flags.includes("--force"), check: flags.includes("--check"), flags },
|
|
2415
|
+
cwd
|
|
2416
|
+
);
|
|
2224
2417
|
case "check-theme":
|
|
2225
2418
|
return checkTheme(cwd, flags);
|
|
2226
2419
|
case "check-coverage":
|
|
@@ -2236,6 +2429,10 @@ function run(argv, cwd = process.cwd()) {
|
|
|
2236
2429
|
case "fonts:css":
|
|
2237
2430
|
return fontsCss(flags, cwd);
|
|
2238
2431
|
default:
|
|
2432
|
+
console.error(
|
|
2433
|
+
cmd === void 0 ? "basalt-ui: no command given.\n" : `basalt-ui: unknown command '${cmd}'.
|
|
2434
|
+
`
|
|
2435
|
+
);
|
|
2239
2436
|
console.error(USAGE);
|
|
2240
2437
|
return 1;
|
|
2241
2438
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipped-versions.test.d.ts","sourceRoot":"","sources":["../../src/cli/shipped-versions.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE7E,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
|
package/dist/guard/index.js
CHANGED
|
@@ -108,8 +108,28 @@ const RADIUS_PROP_RE = /\bradius=(?:\{[0-9]+\}|"[0-9]+")/g;
|
|
|
108
108
|
const MOTION_TRANSITION_NUMERIC = /\btransition\s*=\s*\{\{[^}]*\b(?:duration|stiffness|damping|mass)\s*:\s*-?\d/g;
|
|
109
109
|
const MOTION_TRANSITION_EASE_ARRAY = /\btransition\s*=\s*\{\{[^}]*\bease\s*:\s*\[/g;
|
|
110
110
|
const RAW_CHART_LEGEND_ARRAY = /<ChartLegend\b[^>]*?\bitems\s*=\s*\{\s*\[/g;
|
|
111
|
-
const CHART_ENTRY_POINT_TAG = /<(
|
|
111
|
+
const CHART_ENTRY_POINT_TAG = /<(MultiLine|Bars|BandStrip|Donut|DualPanel|Heatmap|MirroredBars|ZonedLine|StackedArea|LineSparkline|BarSparkline)\b(?:<[^<>]*>)?(?:=>|[^>])*?>/g;
|
|
112
112
|
const HAS_ARIA_LABEL_PROP = /\bariaLabel\s*=/;
|
|
113
|
+
const LOCAL_COMPONENT_DEF = /(?:^|[\n;}])\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var)\s+([A-Z]\w*)/g;
|
|
114
|
+
const BASALT_NAMED_IMPORT = /import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"]basalt-ui(?:\/[^'"]*)?['"]/g;
|
|
115
|
+
function localComponentNames(codeText) {
|
|
116
|
+
const out = /* @__PURE__ */ new Set();
|
|
117
|
+
for (const m of codeText.matchAll(LOCAL_COMPONENT_DEF)) out.add(m[1]);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function basaltImportedNames(codeText) {
|
|
121
|
+
const out = /* @__PURE__ */ new Set();
|
|
122
|
+
for (const m of codeText.matchAll(BASALT_NAMED_IMPORT)) {
|
|
123
|
+
for (const raw of m[1].split(",")) {
|
|
124
|
+
const spec = raw.trim().replace(/^type\s+/, "");
|
|
125
|
+
if (spec.length === 0) continue;
|
|
126
|
+
const parts = spec.split(/\s+as\s+/);
|
|
127
|
+
const bound = parts[parts.length - 1].trim();
|
|
128
|
+
if (bound.length > 0) out.add(bound);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
113
133
|
const RAW_FORM_CONTROL = /<(?:input|select|textarea)\b/g;
|
|
114
134
|
const RAW_FORM_CONTROL_TAG = /<(?:input|select|textarea)\b(?:=>|[^>])*?>/g;
|
|
115
135
|
const STYLES_INPUT_PART = /\bstyles\s*=\s*\{\{\s*input\s*:\s*\{([^}]*)\}/g;
|
|
@@ -250,10 +270,23 @@ function parseAllowAnnotation(rest, scope) {
|
|
|
250
270
|
return { rules, unknownRules, hasReason: reason.length >= MIN_ALLOW_REASON_LENGTH, scope };
|
|
251
271
|
}
|
|
252
272
|
const MAX_CSS_CONTINUATION_LINES = 8;
|
|
253
|
-
|
|
273
|
+
function commentBlockEnd(lines, index, tokenAt, syntax) {
|
|
274
|
+
const line = lines[index] ?? "";
|
|
275
|
+
if (syntax === "ts" && /\/\/\s*$/.test(line.slice(0, tokenAt))) return index;
|
|
276
|
+
const closer = syntax === "markup" ? "-->" : "*/";
|
|
277
|
+
if (line.slice(tokenAt).includes(closer)) return index;
|
|
278
|
+
for (let ahead = index + 1; ahead < lines.length; ahead++) {
|
|
279
|
+
if ((lines[ahead] ?? "").includes(closer)) return ahead;
|
|
280
|
+
}
|
|
281
|
+
return index;
|
|
282
|
+
}
|
|
254
283
|
const COMMENT_OPENER_BEFORE = /(?:\/\/|\/\*|<!--)\s*$/;
|
|
255
284
|
const COMMENT_ONLY_LINE = /^\s*(?:\{\s*\/\*|\/\/|\/\*|\*|<!--)/;
|
|
256
285
|
const JSX_COMMENT_CLOSE = /\*\/\s*\}\s*$/;
|
|
286
|
+
function isCommentOnly(raw, stripped) {
|
|
287
|
+
const code = stripped.trim();
|
|
288
|
+
return code === "" || COMMENT_ONLY_LINE.test(raw) || code === "}" && JSX_COMMENT_CLOSE.test(raw);
|
|
289
|
+
}
|
|
257
290
|
const ANNOTATION_PREFIX = /(?:^|\/\/|\/\*\*?|<!--|^\s*\*)\s*$/;
|
|
258
291
|
function escapeRegExp(value) {
|
|
259
292
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -273,7 +306,7 @@ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
|
|
|
273
306
|
list.push(annotation);
|
|
274
307
|
byLine.set(index, list);
|
|
275
308
|
};
|
|
276
|
-
const record = (index, annotation, trailing) => {
|
|
309
|
+
const record = (index, annotation, trailing, tokenAt) => {
|
|
277
310
|
declared.push({ line: index + 1, annotation });
|
|
278
311
|
if (annotation.scope === "file") {
|
|
279
312
|
fileScoped.push(annotation);
|
|
@@ -281,11 +314,14 @@ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
|
|
|
281
314
|
}
|
|
282
315
|
add(index, annotation);
|
|
283
316
|
if (!trailing) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
317
|
+
const closesAt = commentBlockEnd(lines, index, tokenAt, syntax);
|
|
318
|
+
for (let ahead = 1; index + ahead < lines.length; ahead++) {
|
|
319
|
+
const at = index + ahead;
|
|
320
|
+
add(at, annotation);
|
|
321
|
+
if (at <= closesAt) continue;
|
|
322
|
+
const raw = lines[at] ?? "";
|
|
288
323
|
if (raw.trim() === "") break;
|
|
324
|
+
if (!isCommentOnly(raw, codeLines[at] ?? "")) break;
|
|
289
325
|
}
|
|
290
326
|
return;
|
|
291
327
|
}
|
|
@@ -301,11 +337,15 @@ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
|
|
|
301
337
|
for (let i = 0; i < lines.length; i++) {
|
|
302
338
|
const line = lines[i] ?? "";
|
|
303
339
|
const stripped = codeLines[i] ?? "";
|
|
304
|
-
const
|
|
305
|
-
const isCommentOnlyLine = code === "" || COMMENT_ONLY_LINE.test(line) || code === "}" && JSX_COMMENT_CLOSE.test(line);
|
|
340
|
+
const isCommentOnlyLine = isCommentOnly(line, stripped);
|
|
306
341
|
if (syntax === "markup") {
|
|
307
342
|
for (const m of line.matchAll(jsonKey)) {
|
|
308
|
-
record(
|
|
343
|
+
record(
|
|
344
|
+
i,
|
|
345
|
+
parseAllowAnnotation(m[2] ?? "", m[1] === void 0 ? "line" : "file"),
|
|
346
|
+
false,
|
|
347
|
+
m.index
|
|
348
|
+
);
|
|
309
349
|
}
|
|
310
350
|
}
|
|
311
351
|
for (const m of line.matchAll(token)) {
|
|
@@ -317,7 +357,8 @@ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
|
|
|
317
357
|
record(
|
|
318
358
|
i,
|
|
319
359
|
parseAllowAnnotation(rest, m[1] === void 0 ? "line" : "file"),
|
|
320
|
-
!isCommentOnlyLine
|
|
360
|
+
!isCommentOnlyLine,
|
|
361
|
+
at
|
|
321
362
|
);
|
|
322
363
|
}
|
|
323
364
|
}
|
|
@@ -943,15 +984,21 @@ function checkSource(text, relPath, cfg) {
|
|
|
943
984
|
});
|
|
944
985
|
}
|
|
945
986
|
}
|
|
946
|
-
|
|
987
|
+
const unframedChartRuns = GUARD_RULES["unframed-chart"].enabled(cfg) && ruleApplies("unframed-chart", relPath);
|
|
988
|
+
const ariaLabelRuns = GUARD_RULES["chart-missing-aria-label"].enabled(cfg) && ruleApplies("chart-missing-aria-label", relPath);
|
|
989
|
+
const localNames = unframedChartRuns || ariaLabelRuns ? localComponentNames(codeText) : /* @__PURE__ */ new Set();
|
|
990
|
+
const basaltNames = unframedChartRuns || ariaLabelRuns ? basaltImportedNames(codeText) : /* @__PURE__ */ new Set();
|
|
991
|
+
const isShippedTag = (tag) => basaltNames.has(tag) || !localNames.has(tag);
|
|
992
|
+
if (unframedChartRuns && isShippedTag("ChartLegend")) {
|
|
947
993
|
for (const m of codeText.matchAll(RAW_CHART_LEGEND_ARRAY)) {
|
|
948
994
|
const lineNo = codeText.slice(0, (m.index ?? 0) + m[0].length).split("\n").length;
|
|
949
995
|
push("unframed-chart", lineNo, "items={[");
|
|
950
996
|
}
|
|
951
997
|
}
|
|
952
|
-
if (
|
|
998
|
+
if (ariaLabelRuns) {
|
|
953
999
|
for (const m of codeText.matchAll(CHART_ENTRY_POINT_TAG)) {
|
|
954
1000
|
const tagText = m[0];
|
|
1001
|
+
if (!isShippedTag(m[1])) continue;
|
|
955
1002
|
if (HAS_ARIA_LABEL_PROP.test(tagText)) continue;
|
|
956
1003
|
const startLine = codeText.slice(0, m.index ?? 0).split("\n").length;
|
|
957
1004
|
const endLine = startLine + (tagText.split("\n").length - 1);
|