@sableui/react 0.3.1 → 0.4.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 +8 -4
- package/CHANGELOG.md +60 -0
- package/CONTRIBUTING.md +2 -2
- package/DESIGN.md +7 -7
- package/README.md +7 -3
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +10 -5
- package/dist/{_contract-o3tUc8Ig.d.ts → _contract-Cn3lUpih.d.ts} +10 -5
- package/dist/blocks/index.js +2 -2
- package/dist/{chunk-YXBOYWBN.js → chunk-C4Z3CF54.js} +1 -1
- package/dist/{chunk-CKNM7KSB.js → chunk-CCUDFLU3.js} +23 -6
- package/dist/{chunk-6LCIBMMG.js → chunk-D2ETQBHV.js} +1 -1
- package/dist/{chunk-MXJX3NZI.js → chunk-EFLC4NA3.js} +4 -4
- package/dist/{chunk-EP55KSYA.js → chunk-EHH4MHO6.js} +6 -6
- package/dist/{chunk-WCFLQVK5.js → chunk-FBSL2LZQ.js} +1 -1
- package/dist/{chunk-X276MF2W.js → chunk-OQACYT2D.js} +2 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +13 -13
- package/dist/patterns/index.js +2 -2
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +2 -2
- package/dist/styles.css +16 -6
- package/dist/{theme-provider-BE-KqUG-.d.ts → theme-provider-CGeiusgu.d.ts} +3 -3
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/atelier/index.js +1 -1
- package/dist/themes/ember/ember.css +8 -5
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +3 -3
- package/dist/themes/index.js +3 -3
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +1 -1
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/sable/index.d.ts +9 -0
- package/dist/themes/sable/index.js +1 -0
- package/dist/themes/{base/base.css → sable/sable.css} +6 -6
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes.css +3 -3
- package/llms/components/button.md +15 -3
- package/llms/components/input.md +1 -1
- package/llms/components/scrub-field.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/theme-selector.md +1 -1
- package/llms/reference/token-intent.md +5 -1
- package/llms/tasks/building-a-page.md +1 -1
- package/llms/tasks/creating-a-preset.md +1 -1
- package/llms/tasks/designing-a-surface.md +1 -1
- package/llms/tasks/getting-started.md +4 -0
- package/llms-full.txt +32 -12
- package/llms.txt +1 -1
- package/package.json +5 -5
- package/registry.json +2456 -18
- package/scripts/agent-setup.mjs +34 -7
- package/scripts/design-presets.mjs +1 -1
- package/scripts/grade.mjs +50 -11
- package/scripts/lint-rules.mjs +20 -1
- package/dist/themes/base/index.d.ts +0 -9
- package/dist/themes/base/index.js +0 -1
package/scripts/agent-setup.mjs
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
|
-
import { dirname, join } from "node:path"
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
4
4
|
|
|
5
5
|
const hash = content => createHash("sha256").update(content).digest("hex")
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const RESOLVE = `node -p "require.resolve('@sableui/react/registry.json')"`
|
|
8
|
+
const CONTEXT = "From the target project's dependency context, run:"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The entry as written at `root`. When Sable is a dependency of a workspace
|
|
12
|
+
* package rather than of the root, a bare `require.resolve` from the root fails
|
|
13
|
+
* (pnpm does not hoist it), so the entry names the package and resolves through it.
|
|
14
|
+
*/
|
|
15
|
+
function entryContent(source, projectPath) {
|
|
16
|
+
if (!projectPath) return source
|
|
17
|
+
if (!source.includes(RESOLVE) || !source.includes(CONTEXT)) throw new Error("The installed skill entry has an unexpected shape. Reinstall Sable.")
|
|
18
|
+
return source
|
|
19
|
+
.replace(CONTEXT, `Sable is installed in this repository's \`${projectPath}\` package. From the repository root, run:`)
|
|
20
|
+
.replace(RESOLVE, `node -p "require.resolve('@sableui/react/registry.json', { paths: ['${projectPath}'] })"`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Install discoverable loaders; the installed package owns the actual workflow.
|
|
25
|
+
*
|
|
26
|
+
* `project` is where Sable is a dependency (the directory setup runs in); `root`
|
|
27
|
+
* is where the agent entries go. They differ in a monorepo, where agents start at
|
|
28
|
+
* the repository root but Sable belongs to one workspace package.
|
|
29
|
+
*/
|
|
30
|
+
export function installAgentSkill({ root, project = root, packageRoot, target = "all", update = false, dryRun = false }) {
|
|
9
31
|
const reject = (message) => { throw new Error(message) }
|
|
10
32
|
if (!["all", "codex", "claude", "cursor"].includes(target)) reject("Choose --target all, codex, claude, or cursor.")
|
|
11
|
-
if (!existsSync(join(
|
|
33
|
+
if (!existsSync(join(project, "package.json"))) reject("Run setup from a project with a package.json.")
|
|
34
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) reject(`--root is not a directory: ${root}`)
|
|
35
|
+
const projectPath = relative(resolve(root), resolve(project)).replaceAll("\\", "/")
|
|
36
|
+
if (projectPath.startsWith("..") || isAbsolute(projectPath)) reject("--root must be the project directory or one of its ancestors, such as the repository root.")
|
|
37
|
+
if (/['"\\`]/.test(projectPath)) reject(`Unsupported characters in the project path: ${projectPath}`)
|
|
12
38
|
const canonical = join(packageRoot, "skills/sable/SKILL.md")
|
|
13
39
|
const entry = join(packageRoot, "skills/entry/SKILL.md")
|
|
14
40
|
if (!existsSync(canonical) || !existsSync(entry)) reject("The installed package is missing its skill. Reinstall Sable.")
|
|
@@ -29,8 +55,9 @@ export function installAgentSkill({ root, packageRoot, target = "all", update =
|
|
|
29
55
|
receipt = JSON.parse(readFileSync(join(root, receiptPath), "utf8"))
|
|
30
56
|
if (receipt.schemaVersion !== 1 || !receipt.files || typeof receipt.files !== "object") reject("Invalid Sable setup receipt.")
|
|
31
57
|
}
|
|
58
|
+
const skill = entryContent(readFileSync(entry, "utf8"), projectPath)
|
|
32
59
|
const files = destinations.flatMap(destination => [
|
|
33
|
-
{ path: `${destination}/SKILL.md`, content:
|
|
60
|
+
{ path: `${destination}/SKILL.md`, content: skill },
|
|
34
61
|
...(destination.startsWith(".agents/") ? [{ path: `${destination}/agents/openai.yaml`, content: readFileSync(join(packageRoot, "skills/sable/agents/openai.yaml"), "utf8") }] : []),
|
|
35
62
|
])
|
|
36
63
|
// Preflight every host and the receipt before any writes; preserve local edits.
|
|
@@ -50,5 +77,5 @@ export function installAgentSkill({ root, packageRoot, target = "all", update =
|
|
|
50
77
|
mkdirSync(dirname(join(root, receiptPath)), { recursive: true })
|
|
51
78
|
writeFileSync(join(root, receiptPath), JSON.stringify(receipt, null, 2) + "\n")
|
|
52
79
|
}
|
|
53
|
-
return { destination: destinations[0], destinations, files: files.map(file => file.path), dryRun, update, next: "Use $sable build <your brief> in Codex or /sable build <your brief> in Claude Code or Cursor." }
|
|
80
|
+
return { destination: destinations[0], destinations, root, project: projectPath || ".", files: files.map(file => file.path), dryRun, update, next: "Use $sable build <your brief> in Codex or /sable build <your brief> in Claude Code or Cursor." }
|
|
54
81
|
}
|
|
@@ -15,7 +15,7 @@ try {
|
|
|
15
15
|
const action = modes[0]
|
|
16
16
|
if (action === "--template") {
|
|
17
17
|
const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"))
|
|
18
|
-
const css = readFileSync(resolve(packageRoot, "dist/themes/
|
|
18
|
+
const css = readFileSync(resolve(packageRoot, "dist/themes/sable/sable.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "")
|
|
19
19
|
const groups = { light: {}, dark: {} }
|
|
20
20
|
for (const match of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
21
21
|
const target = match[1].includes(".dark") ? groups.dark : groups.light
|
package/scripts/grade.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
|
|
|
33
33
|
import { createHash } from "node:crypto"
|
|
34
34
|
import { dirname, extname, join, relative, resolve } from "node:path"
|
|
35
35
|
import { fileURLToPath } from "node:url"
|
|
36
|
-
import { lintText, makeRules } from "./lint-rules.mjs"
|
|
36
|
+
import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
|
|
37
37
|
import { skeletonize } from "./skeleton.mjs"
|
|
38
38
|
import { assessReview, reviewTemplate } from "./review-evidence.mjs"
|
|
39
39
|
|
|
@@ -44,8 +44,9 @@ const CWD = process.cwd()
|
|
|
44
44
|
const argv = process.argv.slice(2)
|
|
45
45
|
const flags = new Map()
|
|
46
46
|
const targets = []
|
|
47
|
+
const ignores = []
|
|
47
48
|
const BOOLEAN_FLAGS = new Set(["json", "help", "template", "strict"])
|
|
48
|
-
const VALUE_FLAGS = new Set(["cli-log", "review"])
|
|
49
|
+
const VALUE_FLAGS = new Set(["cli-log", "review", "icons"])
|
|
49
50
|
const JSON_OUT = argv.includes("--json")
|
|
50
51
|
function abort(message) {
|
|
51
52
|
const error = { type: "error", code: "ERR_GRADE_INPUT", message, suggestions: ["sable grade --help", "sable grade --template"] }
|
|
@@ -55,7 +56,11 @@ function abort(message) {
|
|
|
55
56
|
for (let i = 0; i < argv.length; i++) {
|
|
56
57
|
const a = argv[i]
|
|
57
58
|
if (!a.startsWith("--")) targets.push(a)
|
|
58
|
-
else if (
|
|
59
|
+
else if (a === "--ignore" || a.startsWith("--ignore=")) {
|
|
60
|
+
const glob = a === "--ignore" ? argv[++i] : a.slice(9)
|
|
61
|
+
if (!glob || glob.startsWith("--")) abort("--ignore requires a glob.")
|
|
62
|
+
ignores.push(glob)
|
|
63
|
+
} else if (BOOLEAN_FLAGS.has(a.slice(2))) flags.set(a.slice(2), true)
|
|
59
64
|
else if (VALUE_FLAGS.has(a.slice(2))) {
|
|
60
65
|
if (!argv[i + 1] || argv[i + 1].startsWith("--")) abort(`${a} requires a file path.`)
|
|
61
66
|
flags.set(a.slice(2), argv[++i])
|
|
@@ -72,12 +77,14 @@ if (flags.has("help") || !targets.length) {
|
|
|
72
77
|
[
|
|
73
78
|
"grade — check the static contract and recorded review evidence. No model calls.",
|
|
74
79
|
"",
|
|
75
|
-
" sable grade <dir...> [--json] [--review <review.json>] [--strict]",
|
|
80
|
+
" sable grade <dir...> [--json] [--review <review.json>] [--icons <icons.json>] [--ignore <glob>] [--strict]",
|
|
76
81
|
"",
|
|
77
82
|
" --json machine-readable scorecard",
|
|
78
83
|
" --cli-log a SABLE_CLI_LOG jsonl, to report whether the CLI was used at all",
|
|
79
84
|
" --template print a review JSON template; fill after inspecting the rendered build",
|
|
80
85
|
" --review validate evidence files and observations; paths are relative to this JSON",
|
|
86
|
+
' --icons icon names the app adds with composeIconSets: { "names": [...] }. Default: .sable/icons.json',
|
|
87
|
+
" --ignore skip matching paths in the checks, as sable-lint --ignore (repeatable)",
|
|
81
88
|
" --strict exit 1 for static findings or a missing/incomplete/unresolved review",
|
|
82
89
|
"",
|
|
83
90
|
"Checks: design-contract violations · hallucinated components and props ·",
|
|
@@ -102,7 +109,27 @@ const motionTokens = new Set(
|
|
|
102
109
|
].filter(Boolean),
|
|
103
110
|
)
|
|
104
111
|
const RULES = makeRules({ semanticTokens, motionTokens }).filter((r) => r.scope !== "package")
|
|
112
|
+
const PROP_SURFACE = REGISTRY.propSurface?.items ?? {}
|
|
105
113
|
const ICONS = new Set(REGISTRY.iconContract?.names ?? [])
|
|
114
|
+
/**
|
|
115
|
+
* The names an app adds with `composeIconSets(appExtras, reiconIconSet)`. That is
|
|
116
|
+
* the sanctioned way to extend the set, and the grade cannot read a set's names
|
|
117
|
+
* out of a render function, so the app declares them once. Without this every
|
|
118
|
+
* domain icon read as off-contract.
|
|
119
|
+
*/
|
|
120
|
+
const APP_ICONS = (() => {
|
|
121
|
+
const explicit = flags.get("icons")
|
|
122
|
+
const path = resolve(CWD, explicit ?? ".sable/icons.json")
|
|
123
|
+
if (!existsSync(path)) {
|
|
124
|
+
if (explicit) abort(`Not found: ${explicit}`)
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
let names
|
|
128
|
+
try { names = JSON.parse(readFileSync(path, "utf8")).names }
|
|
129
|
+
catch (error) { abort(`Cannot read ${relative(CWD, path)}: ${error.message}`) }
|
|
130
|
+
if (!Array.isArray(names) || !names.every((n) => typeof n === "string" && n)) abort(`${relative(CWD, path)} must be { "names": ["icon-name", …] }.`)
|
|
131
|
+
return { file: relative(CWD, path).replaceAll("\\", "/"), names: new Set(names) }
|
|
132
|
+
})()
|
|
106
133
|
const PATTERNS = REGISTRY.patterns?.items ?? []
|
|
107
134
|
|
|
108
135
|
/**
|
|
@@ -173,7 +200,13 @@ for (const t of targets) {
|
|
|
173
200
|
else snapshotFiles.push(abs)
|
|
174
201
|
}
|
|
175
202
|
snapshotFiles.splice(0, snapshotFiles.length, ...new Set(snapshotFiles.sort()))
|
|
176
|
-
|
|
203
|
+
/* --ignore takes the same globs sable-lint does, so the grade judges the same
|
|
204
|
+
files the lint does. A data layer (palette fixtures, document colours) is
|
|
205
|
+
skipped by the checks but still fingerprinted: it is part of the build. */
|
|
206
|
+
const ignoreRes = ignores.map(ignoreGlob)
|
|
207
|
+
const isIgnored = (file) => ignoreRes.some((re) => re.test(relative(CWD, file).replaceAll("\\", "/")))
|
|
208
|
+
const ignoredFiles = snapshotFiles.filter((file) => CODE.test(file) && isIgnored(file))
|
|
209
|
+
const files = snapshotFiles.filter((file) => CODE.test(file) && !isIgnored(file))
|
|
177
210
|
if (!files.length) abort("No source files found.")
|
|
178
211
|
if (!files.some((file) => readFileSync(file, "utf8").trim().length > 0)) abort("No non-empty source content found.")
|
|
179
212
|
// Git checkouts may use CRLF on Windows and LF in CI. Normalize only known text
|
|
@@ -275,11 +308,14 @@ for (const file of files) {
|
|
|
275
308
|
}
|
|
276
309
|
}
|
|
277
310
|
|
|
278
|
-
/* 3. Props set on a Sable component that its contract does not declare.
|
|
311
|
+
/* 3. Props set on a Sable component that its contract does not declare.
|
|
312
|
+
The contract is the component's real type (registry → propSurface),
|
|
313
|
+
which includes what it forwards to Base UI — Toggle's `pressed`,
|
|
314
|
+
Combobox's `filter` — plus its documented table. */
|
|
279
315
|
for (const m of text.matchAll(/<([A-Z][\w]*)[\s/>]/g)) {
|
|
280
316
|
const comp = REGISTRY.components?.find((c) => c.name === m[1])
|
|
281
317
|
if (!comp?.props?.length) continue
|
|
282
|
-
const declared = new Set(comp.props.map((p) => p.name))
|
|
318
|
+
const declared = new Set([...comp.props.map((p) => p.name), ...(PROP_SURFACE[m[1]] ?? [])])
|
|
283
319
|
for (const sub of comp.subcomponents ?? []) {
|
|
284
320
|
if (typeof sub !== "string") for (const p of sub.props ?? []) declared.add(p.name)
|
|
285
321
|
}
|
|
@@ -307,9 +343,10 @@ for (const file of files) {
|
|
|
307
343
|
if (px >= 20 && px <= 56) findings.hardcodedControl.push({ file: rel, match: m[0], line: lineOf(text, m.index) })
|
|
308
344
|
}
|
|
309
345
|
|
|
310
|
-
/* 6. Icon names outside the contract render nothing at all
|
|
346
|
+
/* 6. Icon names outside the contract render nothing at all — unless the app
|
|
347
|
+
supplies them itself and has declared them (APP_ICONS). */
|
|
311
348
|
for (const m of text.matchAll(/<Icon\s+[^>]*name=["']([^"']+)["']/g)) {
|
|
312
|
-
if (!ICONS.has(m[1])) findings.offContractIcon.push({ file: rel, name: m[1], line: lineOf(text, m.index) })
|
|
349
|
+
if (!ICONS.has(m[1]) && !APP_ICONS?.names.has(m[1])) findings.offContractIcon.push({ file: rel, name: m[1], line: lineOf(text, m.index) })
|
|
313
350
|
}
|
|
314
351
|
|
|
315
352
|
/* 7. Composition. Anything exporting a component that returns a big tree is
|
|
@@ -438,6 +475,7 @@ const result = {
|
|
|
438
475
|
sourceHash,
|
|
439
476
|
fingerprint: { algorithm: "sha256", files: snapshotFiles.length, packageHash, coverage: "All non-test files in supplied targets, plus Sable runtime sources, registry, and lint contract. Known text formats normalize CRLF to LF; binary assets retain their bytes. Pass public/ for external asset directories; remote resources are not covered." },
|
|
440
477
|
files: files.length,
|
|
478
|
+
ignored: { globs: ignores, files: ignoredFiles.length },
|
|
441
479
|
loc,
|
|
442
480
|
score,
|
|
443
481
|
scoreMeaning: "Static contract only. Pattern similarity does not affect this score; visual quality is not scored.",
|
|
@@ -447,6 +485,7 @@ const result = {
|
|
|
447
485
|
per100loc: Object.fromEntries(Object.entries(counts).map(([k, v]) => [k, per100(v)])),
|
|
448
486
|
conformance: { informationalOnly: true, mean: meanConformance, routes: conformance },
|
|
449
487
|
cliUsage,
|
|
488
|
+
appIcons: APP_ICONS ? { file: APP_ICONS.file, names: APP_ICONS.names.size } : null,
|
|
450
489
|
findings,
|
|
451
490
|
}
|
|
452
491
|
|
|
@@ -459,7 +498,7 @@ if (JSON_OUT) {
|
|
|
459
498
|
|
|
460
499
|
const L = (s = "") => process.stdout.write(`${s}\n`)
|
|
461
500
|
L()
|
|
462
|
-
L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}`)
|
|
501
|
+
L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}${ignoredFiles.length ? ` (${ignoredFiles.length} ignored)` : ""}`)
|
|
463
502
|
L(` ${"─".repeat(66)}`)
|
|
464
503
|
const row = (label, n, note = "") =>
|
|
465
504
|
L(` ${label.padEnd(26)}${String(n).padStart(4)}${note ? ` ${note}` : ""}`)
|
|
@@ -468,7 +507,7 @@ row("hallucinated components", counts.hallucinatedComponent)
|
|
|
468
507
|
row("hallucinated props", counts.hallucinatedProp)
|
|
469
508
|
row("Radix idioms", counts.radixIdiom, counts.radixIdiom ? "asChild / direct primitive imports" : "")
|
|
470
509
|
row("hardcoded control heights", counts.hardcodedControl)
|
|
471
|
-
row("off-contract icon names", counts.offContractIcon)
|
|
510
|
+
row("off-contract icon names", counts.offContractIcon, APP_ICONS ? `+${APP_ICONS.names.size} app icons from ${APP_ICONS.file}` : counts.offContractIcon ? "app-supplied? list them in .sable/icons.json" : "")
|
|
472
511
|
L()
|
|
473
512
|
if (conformance.length) {
|
|
474
513
|
L(` pattern similarity (informational) — ${conformance.length} route-like file(s), mean ${meanConformance}`)
|
package/scripts/lint-rules.mjs
CHANGED
|
@@ -537,10 +537,14 @@ export function makeRules({ semanticTokens, motionTokens }) {
|
|
|
537
537
|
since: "0.1.0",
|
|
538
538
|
code: true,
|
|
539
539
|
why:
|
|
540
|
-
"A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason."
|
|
540
|
+
"A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason. " +
|
|
541
|
+
"Exempt: the value of a `--theme-*` hook (`--theme-panel-field: color-mix(…)`), which is how a theme tunes a derived token.",
|
|
541
542
|
find(line) {
|
|
542
543
|
const out = []
|
|
543
544
|
for (const m of line.matchAll(/color-mix\([^;"'`]*?var\(--foreground\)/g)) {
|
|
545
|
+
// A theme hook is the sanctioned place to retune a derived surface,
|
|
546
|
+
// in CSS or as an arbitrary property ([--theme-x:color-mix(…)]).
|
|
547
|
+
if (/--theme-[\w-]+\s*:\s*$/.test(line.slice(0, m.index))) continue
|
|
544
548
|
out.push({ match: m[0].length > 60 ? `${m[0].slice(0, 57)}…` : m[0] })
|
|
545
549
|
}
|
|
546
550
|
return out
|
|
@@ -964,6 +968,21 @@ function stripComments(src) {
|
|
|
964
968
|
.replace(/(^|[^:])\/\/[^\n]*/gm, (m, p) => p + " ".repeat(m.length - p.length))
|
|
965
969
|
}
|
|
966
970
|
|
|
971
|
+
/**
|
|
972
|
+
* An `--ignore` glob as a RegExp over a CWD-relative, forward-slash path:
|
|
973
|
+
* `**` crosses directories, `*` stays within one. Shared by sable-lint and
|
|
974
|
+
* sable grade so the same flag skips the same files in both.
|
|
975
|
+
*/
|
|
976
|
+
export function ignoreGlob(glob) {
|
|
977
|
+
return new RegExp(
|
|
978
|
+
`^${glob
|
|
979
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
980
|
+
.replace(/\*\*/g, "\0")
|
|
981
|
+
.replace(/\*/g, "[^/]*")
|
|
982
|
+
.replace(/\0/g, ".*")}$`,
|
|
983
|
+
)
|
|
984
|
+
}
|
|
985
|
+
|
|
967
986
|
export function lintText(text, rules, ctx) {
|
|
968
987
|
const lines = text.split(/\r?\n/)
|
|
969
988
|
const codeLines = stripComments(text).split(/\r?\n/)
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { T as ThemeManifest } from '../../_contract-o3tUc8Ig.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base — the default Sable theme. Concrete token values live in
|
|
5
|
-
* `base.css`; this manifest is the typed, machine-readable description.
|
|
6
|
-
*/
|
|
7
|
-
declare const baseTheme: ThemeManifest;
|
|
8
|
-
|
|
9
|
-
export { baseTheme, baseTheme as default };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { baseTheme, base_default as default } from '../../chunk-EP55KSYA.js';
|