basalt-ui 1.21.0 → 1.22.0
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 +3 -1
- package/CHANGELOG.md +14 -0
- package/MIGRATING.md +93 -10
- package/README.md +20 -7
- package/agent/rules/basalt-app.md +15 -9
- package/agent/rules/basalt-charts.md +2 -2
- package/agent/rules/basalt-router.md +1 -1
- package/agent/rules/basalt-state.md +4 -4
- package/agent/rules/basalt-tokens.md +9 -4
- package/agent/skills/basalt-app/SKILL.md +4 -3
- package/agent/skills/basalt-charts/SKILL.md +1 -1
- package/configs/oxlint-plugin.js +37 -1
- package/configs/oxlint-plugin.test.ts +116 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +230 -42
- package/dist/guard/index.d.ts +67 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +40 -2
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +21 -13
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/cli/check-theme.test.ts +35 -0
- package/src/cli/help.test.ts +5 -1
- package/src/cli/index.ts +434 -68
- package/src/cli/toolchain-wiring.test.ts +192 -3
- package/src/cli/waiver-audit.test.ts +84 -4
- package/src/guard/check-source.test.ts +197 -0
- package/src/guard/index.ts +129 -6
- package/src/vite.test.ts +33 -0
- package/src/vite.ts +28 -14
package/dist/guard/index.js
CHANGED
|
@@ -253,7 +253,8 @@ const MAX_CSS_CONTINUATION_LINES = 8;
|
|
|
253
253
|
const MAX_COMMENT_BLOCK_LINES = 8;
|
|
254
254
|
const COMMENT_OPENER_BEFORE = /(?:\/\/|\/\*|<!--)\s*$/;
|
|
255
255
|
const COMMENT_ONLY_LINE = /^\s*(?:\{\s*\/\*|\/\/|\/\*|\*|<!--)/;
|
|
256
|
-
const
|
|
256
|
+
const JSX_COMMENT_CLOSE = /\*\/\s*\}\s*$/;
|
|
257
|
+
const ANNOTATION_PREFIX = /(?:^|\/\/|\/\*\*?|<!--|^\s*\*)\s*$/;
|
|
257
258
|
function escapeRegExp(value) {
|
|
258
259
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
259
260
|
}
|
|
@@ -300,7 +301,8 @@ function collectAllowAnnotations(lines, codeLines, allowComment, syntax) {
|
|
|
300
301
|
for (let i = 0; i < lines.length; i++) {
|
|
301
302
|
const line = lines[i] ?? "";
|
|
302
303
|
const stripped = codeLines[i] ?? "";
|
|
303
|
-
const
|
|
304
|
+
const code = stripped.trim();
|
|
305
|
+
const isCommentOnlyLine = code === "" || COMMENT_ONLY_LINE.test(line) || code === "}" && JSX_COMMENT_CLOSE.test(line);
|
|
304
306
|
if (syntax === "markup") {
|
|
305
307
|
for (const m of line.matchAll(jsonKey)) {
|
|
306
308
|
record(i, parseAllowAnnotation(m[2] ?? "", m[1] === void 0 ? "line" : "file"), false);
|
|
@@ -997,13 +999,49 @@ function checkSource(text, relPath, cfg) {
|
|
|
997
999
|
severity: severityOf(f.kind, cfg)
|
|
998
1000
|
}));
|
|
999
1001
|
}
|
|
1002
|
+
function findAllowAnnotations(text, relPath, cfg) {
|
|
1003
|
+
const lines = text.split("\n");
|
|
1004
|
+
const syntax = guardSyntaxFor(relPath);
|
|
1005
|
+
const codeText = syntax === "markup" ? stripMarkupComments(text) : stripComments(text, syntax);
|
|
1006
|
+
const { declared } = collectAllowAnnotations(
|
|
1007
|
+
lines,
|
|
1008
|
+
codeText.split("\n"),
|
|
1009
|
+
cfg.allowComment,
|
|
1010
|
+
syntax
|
|
1011
|
+
);
|
|
1012
|
+
return declared.map(({ line, annotation }) => ({
|
|
1013
|
+
line,
|
|
1014
|
+
scope: annotation.scope,
|
|
1015
|
+
rules: [...annotation.rules],
|
|
1016
|
+
guardKinds: annotation.rules.filter((id) => Object.hasOwn(GUARD_RULES, id)),
|
|
1017
|
+
pluginRules: annotation.rules.filter(
|
|
1018
|
+
(id) => !Object.hasOwn(GUARD_RULES, id) && PLUGIN_RULE_IDS.has(id)
|
|
1019
|
+
),
|
|
1020
|
+
unknownRules: [...annotation.unknownRules],
|
|
1021
|
+
hasReason: annotation.hasReason,
|
|
1022
|
+
bare: annotation.rules.length === 0 && annotation.unknownRules.length === 0,
|
|
1023
|
+
text: lines[line - 1] ?? ""
|
|
1024
|
+
}));
|
|
1025
|
+
}
|
|
1026
|
+
const NEUTRALIZED_ALLOW_TOKEN = "basalt-audit-neutralized";
|
|
1027
|
+
function neutralizeAllowAnnotation(text, line, cfg) {
|
|
1028
|
+
const lines = text.split("\n");
|
|
1029
|
+
const target = lines[line - 1];
|
|
1030
|
+
if (target === void 0) return text;
|
|
1031
|
+
lines[line - 1] = target.replaceAll(cfg.allowComment, NEUTRALIZED_ALLOW_TOKEN);
|
|
1032
|
+
return lines.join("\n");
|
|
1033
|
+
}
|
|
1000
1034
|
export {
|
|
1001
1035
|
DEFAULT_GUARD_CONFIG,
|
|
1002
1036
|
GENERATED_HEADER_LINE,
|
|
1003
1037
|
GUARD_RULES,
|
|
1038
|
+
NEUTRALIZED_ALLOW_TOKEN,
|
|
1039
|
+
PLUGIN_RULE_IDS,
|
|
1004
1040
|
TOKENS_ONLY_DISABLED_KINDS,
|
|
1005
1041
|
checkSource,
|
|
1042
|
+
findAllowAnnotations,
|
|
1006
1043
|
guardKindRemedy,
|
|
1007
1044
|
guardWaiverHint,
|
|
1045
|
+
neutralizeAllowAnnotation,
|
|
1008
1046
|
unmatchedExemptPatterns
|
|
1009
1047
|
};
|
package/dist/vite.d.ts
CHANGED
|
@@ -80,7 +80,10 @@ export type BasaltAppOptions = {
|
|
|
80
80
|
scope?: string;
|
|
81
81
|
/** Manifest `id`. Default: the resolved `startUrl` (default- or explicitly-derived, per above). */
|
|
82
82
|
id?: string;
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Icon links + manifest icon paths. `false` skips BOTH — the head `<link>` icons and the
|
|
85
|
+
* manifest's `icons` array — so a manifest is never emitted pointing at files that 404.
|
|
86
|
+
*/
|
|
84
87
|
icons?: false | {
|
|
85
88
|
dir?: string;
|
|
86
89
|
};
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA6CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAgFpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IAC/C;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA6CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAgFpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IAC/C;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;KAAE,CAAA;IACvF,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClD,CAAA;AA4gBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAMzE"}
|
package/dist/vite.js
CHANGED
|
@@ -296,6 +296,7 @@ function buildManifestJson(input) {
|
|
|
296
296
|
display,
|
|
297
297
|
schemeColor,
|
|
298
298
|
backgroundColor,
|
|
299
|
+
iconsEnabled,
|
|
299
300
|
iconsDir,
|
|
300
301
|
base
|
|
301
302
|
} = input;
|
|
@@ -311,19 +312,25 @@ function buildManifestJson(input) {
|
|
|
311
312
|
// `<meta name="theme-color">` pair. Unchanged for the default (`'dark'`) and for `'auto'`.
|
|
312
313
|
theme_color: schemeColor,
|
|
313
314
|
background_color: backgroundColor,
|
|
314
|
-
icons:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
315
|
+
// `icons: false` reaches HERE too, not just the head links. The array was unconditional, so
|
|
316
|
+
// `{ manifest: true, icons: false }` shipped a manifest pointing at two PNGs the app never
|
|
317
|
+
// ships — an installable app with broken icons, and the reason rb went hybrid (plugin for the
|
|
318
|
+
// head, a hand-written manifest for the rest). A manifest with NO icons member is valid.
|
|
319
|
+
...iconsEnabled ? {
|
|
320
|
+
icons: [
|
|
321
|
+
{
|
|
322
|
+
src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest192)),
|
|
323
|
+
sizes: "192x192",
|
|
324
|
+
type: "image/png"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
src: withBase(base, withIconPath(iconsDir, ICON_FILES.manifest512)),
|
|
328
|
+
sizes: "512x512",
|
|
329
|
+
type: "image/png",
|
|
330
|
+
purpose: "any maskable"
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
} : {}
|
|
327
334
|
};
|
|
328
335
|
return JSON.stringify(manifest, null, 2);
|
|
329
336
|
}
|
|
@@ -364,6 +371,7 @@ function createMainPlugin(options) {
|
|
|
364
371
|
display,
|
|
365
372
|
schemeColor,
|
|
366
373
|
backgroundColor,
|
|
374
|
+
iconsEnabled,
|
|
367
375
|
iconsDir,
|
|
368
376
|
base
|
|
369
377
|
});
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.
|
|
1
|
+
# basalt-ui v1.22.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.
|
|
22
|
+
Version: 1.22.0
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
|
@@ -742,3 +742,38 @@ describe('severity — warnings report, errors fail', () => {
|
|
|
742
742
|
expect(err).toContain('✖ Theme guard')
|
|
743
743
|
})
|
|
744
744
|
})
|
|
745
|
+
|
|
746
|
+
// ── the manifest waiver hint, gated on the profile ────────────────────────────────────────────
|
|
747
|
+
//
|
|
748
|
+
// rollhook. The hint leads with `basaltAppPlugin`, which is right for a Mantine app — a hex in a
|
|
749
|
+
// manifest can never be right, and the plugin removes the hand-copy entirely. A `tokens-only`
|
|
750
|
+
// consumer has definitionally opted out of that layer: rollhook's Astro site has no index.html to
|
|
751
|
+
// transform, imports no basalt JavaScript at all, and owns maskable icons the plugin does not emit.
|
|
752
|
+
// Leading with it there is advice the consumer cannot take.
|
|
753
|
+
|
|
754
|
+
describe('check-theme — the manifest waiver hint follows the profile', () => {
|
|
755
|
+
function manifestFixture(basalt: Record<string, unknown>): void {
|
|
756
|
+
writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ name: 'fixture', basalt }))
|
|
757
|
+
mkdirSync(resolve(dir, 'src'), { recursive: true })
|
|
758
|
+
writeFileSync(resolve(dir, 'src', 'app.tsx'), 'export const App = () => null\n')
|
|
759
|
+
mkdirSync(resolve(dir, 'public'), { recursive: true })
|
|
760
|
+
writeFileSync(
|
|
761
|
+
resolve(dir, 'public', 'site.webmanifest'),
|
|
762
|
+
JSON.stringify({ name: 'x', theme_color: '#18181b' }, null, 2),
|
|
763
|
+
)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
it('offers basaltAppPlugin first to a framework consumer', () => {
|
|
767
|
+
manifestFixture({ roots: ['src'] })
|
|
768
|
+
const { err } = run()
|
|
769
|
+
expect(err).toContain('let basaltAppPlugin emit it')
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it('leads with the member for a tokens-only consumer, and says which remedy it withheld', () => {
|
|
773
|
+
manifestFixture({ roots: ['src'], profile: 'tokens-only' })
|
|
774
|
+
const { err } = run()
|
|
775
|
+
expect(err).toContain('JSON has no comments — declare a deliberate exception')
|
|
776
|
+
expect(err).not.toContain('let basaltAppPlugin emit it')
|
|
777
|
+
expect(err).toContain('a tokens-only consumer has opted out of')
|
|
778
|
+
})
|
|
779
|
+
})
|
package/src/cli/help.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* matched `cmd === 'sync'` first and `--help` was just another item in `flags`, ignored).
|
|
5
5
|
*/
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
7
|
-
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
8
8
|
import { tmpdir } from 'node:os'
|
|
9
9
|
import { resolve } from 'node:path'
|
|
10
10
|
|
|
@@ -86,6 +86,10 @@ describe('run() --help short-circuits before dispatch', () => {
|
|
|
86
86
|
})
|
|
87
87
|
|
|
88
88
|
it('`sync` without --help still runs for real and writes the manifest (control case)', () => {
|
|
89
|
+
// `sync` refuses to scaffold where nothing was ever installed, so the control case has to be a
|
|
90
|
+
// repo that HAS an install — which is what `sync` is for.
|
|
91
|
+
mkdirSync(resolve(dir, '.basalt'), { recursive: true })
|
|
92
|
+
writeFileSync(resolve(dir, MANIFEST_PATH), JSON.stringify({ version: 1, files: {} }))
|
|
89
93
|
const { code } = capture(() => run(['sync'], dir))
|
|
90
94
|
expect(code).toBe(0)
|
|
91
95
|
expect(existsSync(resolve(dir, MANIFEST_PATH))).toBe(true)
|