@celestia-island/hikari 0.55.159 → 0.55.160

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.159",
3
+ "version": "0.55.160",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { languageForPath } from "./useHighlight";
4
+ import { LANGUAGE_LOADERS } from "./highlightLanguages";
5
+
6
+ /**
7
+ * `languageForPath` is the ONE mapping from a file path to a highlight.js
8
+ * language — shared so a diff pane, a file browser and a markdown fence
9
+ * all derive the same language from the same path. Its two contracts:
10
+ * every non-empty answer must be a language the lazy loader can actually
11
+ * load (otherwise the promise is a lie), and unknown paths must answer ""
12
+ * (callers treat "" as plaintext/auto-detect, never as an error).
13
+ */
14
+ describe("languageForPath", () => {
15
+ it("maps the common extensions onto canonical ids", () => {
16
+ expect(languageForPath("crates/scheduler/src/main.rs")).toBe("rust");
17
+ expect(languageForPath("src/index.ts")).toBe("typescript");
18
+ expect(languageForPath("App.tsx")).toBe("typescript");
19
+ expect(languageForPath("script.js")).toBe("javascript");
20
+ expect(languageForPath("world.toml")).toBe("toml");
21
+ expect(languageForPath("Cargo.toml")).toBe("toml");
22
+ expect(languageForPath("config/world.yml")).toBe("yaml");
23
+ expect(languageForPath("run.sh")).toBe("bash");
24
+ expect(languageForPath("notes.md")).toBe("markdown");
25
+ expect(languageForPath("query.sql")).toBe("sql");
26
+ expect(languageForPath("theme.scss")).toBe("scss");
27
+ expect(languageForPath("index.html")).toBe("xml");
28
+ });
29
+
30
+ it("is case-insensitive on name and extension", () => {
31
+ expect(languageForPath("src/Main.RS")).toBe("rust");
32
+ expect(languageForPath("Dockerfile")).toBe("dockerfile");
33
+ expect(languageForPath("Makefile")).toBe("makefile");
34
+ expect(languageForPath("CMakeLists.txt")).toBe("cmake");
35
+ expect(languageForPath("Jenkinsfile")).toBe("groovy");
36
+ });
37
+
38
+ it("keeps dotted stems like Dockerfile.dev on their dialect", () => {
39
+ expect(languageForPath("deploy/Dockerfile.dev")).toBe("dockerfile");
40
+ expect(languageForPath("dockerfile.prod")).toBe("dockerfile");
41
+ expect(languageForPath("Makefile.am")).toBe("makefile");
42
+ });
43
+
44
+ it("answers \"\" for unknown or extensionless paths, not a guess", () => {
45
+ expect(languageForPath("bin/run")).toBe("");
46
+ expect(languageForPath("README")).toBe("");
47
+ expect(languageForPath("data.xyzzy")).toBe("");
48
+ expect(languageForPath("")).toBe("");
49
+ });
50
+
51
+ it("every mapped id is a language the loader actually has", () => {
52
+ const loaders = new Set(Object.keys(LANGUAGE_LOADERS));
53
+ expect(loaders.size).toBeGreaterThan(50); // positive control
54
+ const probes = [
55
+ "a.rs", "a.ts", "a.tsx", "a.js", "a.json", "a.py", "a.rb", "a.go",
56
+ "a.java", "a.kt", "a.scala", "a.groovy", "a.c", "a.h", "a.cpp",
57
+ "a.hpp", "a.cs", "a.fs", "a.php", "a.swift", "a.m", "a.mm",
58
+ "a.dart", "a.ex", "a.erl", "a.hs", "a.elm", "a.lua", "a.pl",
59
+ "a.r", "a.jl", "a.nim", "a.cr", "a.ml", "a.vb", "a.pas", "a.bas",
60
+ "a.f90", "a.coffee", "a.ls", "a.clj", "a.scm", "a.el", "a.xml",
61
+ "a.html", "a.vue", "a.hbs", "a.twig", "a.haml", "a.erb", "a.dust",
62
+ "a.css", "a.scss", "a.less", "a.yml", "a.yaml", "a.toml", "a.ini",
63
+ "a.cfg", "a.properties", "a.env", "a.md", "a.graphql", "a.gql",
64
+ "a.http", "a.sh", "a.zsh", "a.ps1", "a.bat", "a.cmd", "a.mk",
65
+ "a.cmake", "a.awk", "a.tcl", "a.vim", "a.sql", "a.psql", "a.proto",
66
+ "a.thrift", "a.glsl", "a.vert", "a.frag", "a.wasm", "a.asm",
67
+ "a.v", "a.sv", "a.vhd", "a.vhdl", "a.tex", "a.sty", "a.diff",
68
+ "a.patch", "a.txt", "a.log", "Dockerfile", "Makefile", "Jenkinsfile",
69
+ "CMakeLists.txt", "Cargo.lock", ".gitignore", ".env", ".bashrc",
70
+ ];
71
+ const mapped = probes
72
+ .map((p) => languageForPath(p))
73
+ .filter((id): id is string => id !== "");
74
+ // Each probe resolves to SOMETHING (the table is broad)…
75
+ expect(mapped.length).toBe(probes.length);
76
+ // …and every answer is loadable — no lying promises.
77
+ for (const id of mapped) {
78
+ expect(loaders, `languageForPath answered "${id}", which no loader provides`).toContain(id);
79
+ }
80
+ });
81
+
82
+ it("does not fall off the path on dots in directories", () => {
83
+ expect(languageForPath("my.dir/stuff/main.rs")).toBe("rust");
84
+ expect(languageForPath("v1.2/config.yaml")).toBe("yaml");
85
+ });
86
+ });
@@ -84,6 +84,125 @@ function ensureAutoLangs(): void {
84
84
  for (const l of AUTO_LANGS) loadLanguage(l);
85
85
  }
86
86
 
87
+ /** Whole filenames (lowercased) that carry no useful extension, or whose
88
+ * dialect is decided by the name: `Dockerfile.dev`, `CMakeLists.txt`. */
89
+ const PATH_FILENAME_LANG: Record<string, string> = {
90
+ dockerfile: "dockerfile",
91
+ makefile: "makefile",
92
+ gnumakefile: "makefile",
93
+ "cmakelists.txt": "cmake",
94
+ jenkinsfile: "groovy",
95
+ nginx: "nginx",
96
+ "cargo.lock": "toml",
97
+ ".env": "ini",
98
+ ".gitignore": "ini",
99
+ ".gitattributes": "ini",
100
+ ".gitmodules": "ini",
101
+ ".editorconfig": "ini",
102
+ ".bashrc": "bash",
103
+ ".zshrc": "bash",
104
+ ".profile": "bash",
105
+ };
106
+
107
+ /** Extension (lowercased) → canonical highlight.js language id. Only ids
108
+ * that exist in LANGUAGE_LOADERS: anything else would render plaintext
109
+ * anyway, just with a misleading promise attached. */
110
+ const EXT_TABLE: Record<string, string> = {
111
+ rs: "rust",
112
+ ts: "typescript", tsx: "typescript", mts: "typescript", cts: "typescript",
113
+ js: "javascript", jsx: "javascript", mjs: "javascript", cjs: "javascript",
114
+ json: "json", jsonc: "json",
115
+ py: "python", pyi: "python", pyw: "python",
116
+ rb: "ruby",
117
+ go: "go",
118
+ java: "java",
119
+ kt: "kotlin", kts: "kotlin",
120
+ scala: "scala", sc: "scala",
121
+ groovy: "groovy", gradle: "groovy",
122
+ c: "c", h: "c",
123
+ cpp: "cpp", cc: "cpp", cxx: "cpp", hpp: "cpp", hh: "cpp", hxx: "cpp",
124
+ cs: "csharp",
125
+ fs: "fsharp", fsx: "fsharp",
126
+ php: "php",
127
+ swift: "swift",
128
+ m: "objectivec", mm: "objectivec",
129
+ dart: "dart",
130
+ ex: "elixir", exs: "elixir",
131
+ erl: "erlang", hrl: "erlang",
132
+ hs: "haskell", lhs: "haskell",
133
+ elm: "elm",
134
+ lua: "lua",
135
+ pl: "perl", pm: "perl",
136
+ r: "r",
137
+ jl: "julia",
138
+ nim: "nim",
139
+ cr: "crystal",
140
+ ml: "ocaml", mli: "ocaml", mll: "ocaml", mly: "ocaml",
141
+ vb: "vbnet",
142
+ pas: "delphi", dpr: "delphi",
143
+ bas: "basic",
144
+ f: "fortran", f90: "fortran", f95: "fortran", f03: "fortran", for: "fortran",
145
+ coffee: "coffeescript",
146
+ ls: "livescript",
147
+ clj: "clojure", cljs: "clojure", cljc: "clojure", edn: "clojure",
148
+ scm: "scheme", ss: "scheme", el: "lisp", lisp: "lisp",
149
+ xml: "xml", html: "xml", htm: "xml", svg: "xml", vue: "xml", xhtml: "xml",
150
+ cshtml: "xml", jsp: "xml", erb: "erb", ejs: "xml",
151
+ hbs: "handlebars", handlebars: "handlebars",
152
+ twig: "twig",
153
+ haml: "haml",
154
+ dust: "dust",
155
+ css: "css", scss: "scss", less: "less",
156
+ yml: "yaml", yaml: "yaml",
157
+ toml: "toml",
158
+ ini: "ini", cfg: "ini", conf: "ini", properties: "properties", env: "ini",
159
+ md: "markdown", markdown: "markdown",
160
+ graphql: "graphql", gql: "graphql",
161
+ http: "http",
162
+ sh: "bash", bash: "bash", zsh: "bash", shell: "bash", fish: "bash",
163
+ ps1: "powershell", psm1: "powershell",
164
+ bat: "dos", cmd: "dos",
165
+ mk: "makefile",
166
+ cmake: "cmake",
167
+ dockerfile: "dockerfile",
168
+ nginx: "nginx",
169
+ awk: "awk",
170
+ tcl: "tcl",
171
+ vim: "vim",
172
+ sql: "sql", psql: "pgsql",
173
+ proto: "protobuf",
174
+ thrift: "thrift",
175
+ glsl: "glsl", vert: "glsl", frag: "glsl", geom: "glsl",
176
+ wasm: "wasm",
177
+ asm: "x86asm",
178
+ v: "verilog", sv: "verilog",
179
+ vhd: "vhdl", vhdl: "vhdl",
180
+ tex: "latex", sty: "latex",
181
+ diff: "diff", patch: "diff",
182
+ txt: "plaintext", text: "plaintext", log: "plaintext",
183
+ };
184
+
185
+ /**
186
+ * A file path → the canonical highlight.js language id for it, or "" when
187
+ * nothing sensible is known (callers pass "" as "no language" and the
188
+ * block renders plaintext or auto-detects). One mapping for every code
189
+ * surface — diff panes, file browsers, markdown fences — so the same
190
+ * path always highlights the same way; aliases are resolved by
191
+ * `normalize()` at highlight time, so the table lists canonical ids.
192
+ */
193
+ export function languageForPath(path: string): string {
194
+ const name = path.split("/").pop()?.trim().toLowerCase() ?? "";
195
+ if (!name) return "";
196
+ // Extensionless build scripts: `Dockerfile`, `Jenkinsfile`, `Makefile`.
197
+ const bare = PATH_FILENAME_LANG[name];
198
+ if (bare) return bare;
199
+ // `Dockerfile.dev` / `dockerfile.prod` — the dialect IS the stem.
200
+ if (name.startsWith("dockerfile")) return "dockerfile";
201
+ if (name.startsWith("makefile")) return "makefile";
202
+ const ext = name.includes(".") ? name.split(".").pop()! : "";
203
+ return EXT_TABLE[ext] ?? "";
204
+ }
205
+
87
206
  /**
88
207
  * Highlight `code` for the given language (or auto-detect when omitted).
89
208
  * Returns highlight.js HTML when the grammar is ready, otherwise an
package/src/index.ts CHANGED
@@ -460,7 +460,7 @@ export type { OriginProbe } from "./utils/connectivity";
460
460
  export { probeHealthEndpoint } from "./utils/healthProbe";
461
461
  export type { HealthProbeBody, HealthProbeResult } from "./utils/healthProbe";
462
462
 
463
- export { highlight, useHighlight } from "./composables/useHighlight";
463
+ export { highlight, languageForPath, useHighlight } from "./composables/useHighlight";
464
464
  export { LANGUAGE_LOADERS } from "./composables/highlightLanguages";
465
465
  export {
466
466
  iconByName,
@@ -0,0 +1,91 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ /**
8
+ * The syntax-palette contract (theme/highlight.scss).
9
+ *
10
+ * `useHighlight()` emits `.hljs-*` spans; this sheet is the ONLY thing
11
+ * that colours them, so the pairs documented in channels.scss must stay
12
+ * mapped to their `--code-*` tokens — if a restyle ever swaps a var() for
13
+ * a bare hex, themed hosts stop flipping light/dark for that class and
14
+ * the sheet silently forks the palette the host owns.
15
+ *
16
+ * Assertions run on the BODY (comment lines stripped): the header and
17
+ * inline comments deliberately NAME the classes they explain, so matching
18
+ * against the raw sheet would hit prose instead of rules.
19
+ */
20
+ const body = readFileSync(
21
+ resolve(dirname(fileURLToPath(import.meta.url)), "theme/highlight.scss"),
22
+ "utf-8",
23
+ )
24
+ .split("\n")
25
+ .filter((line) => !line.trim().startsWith("//"))
26
+ .join("\n");
27
+
28
+ /** Pull one declaration block by a selector fragment (regex-escaped). */
29
+ function blockFor(selector: string): string {
30
+ const esc = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ const re = new RegExp(
32
+ String.raw`\.hljs-${esc}\b[\s\S]*?\{([^}]*)\}`,
33
+ );
34
+ const m = re.exec(body);
35
+ expect(m, `positive control: the .hljs-${selector} rule exists`).not.toBeNull();
36
+ return m![1];
37
+ }
38
+
39
+ describe("highlight palette (theme/highlight.scss)", () => {
40
+ it("rides the --code-* family for every colour, One Dark as fallback", () => {
41
+ const colorRules = body.match(/color:\s*[^;]+;/g) ?? [];
42
+ expect(colorRules.length).toBeGreaterThan(5); // positive control
43
+ for (const rule of colorRules) {
44
+ expect(
45
+ rule,
46
+ `every colour must ride the token family: "${rule}"`,
47
+ ).toMatch(/color:\s*var\(--code-[a-z]+,\s*#[0-9a-fA-F]{6}\);/);
48
+ }
49
+ });
50
+
51
+ it("keeps the pairs channels.scss documents canonical", () => {
52
+ expect(blockFor("keyword")).toContain("var(--code-keyword, #c678dd)");
53
+ expect(blockFor("selector-tag")).toContain("var(--code-keyword, #c678dd)");
54
+ expect(blockFor("built_in")).toContain("var(--code-builtin, #c678dd)");
55
+ expect(blockFor("string")).toContain("var(--code-string, #98c379)");
56
+ expect(blockFor("number")).toContain("var(--code-number, #d19a66)");
57
+ expect(blockFor("literal")).toContain("var(--code-number, #d19a66)");
58
+ expect(blockFor("attr")).toContain("var(--code-number, #d19a66)");
59
+ expect(blockFor("title.function_")).toContain("var(--code-function, #61afef)");
60
+ expect(blockFor("type")).toContain("var(--code-type, #e5c07b)");
61
+ expect(blockFor("title.class_")).toContain("var(--code-type, #e5c07b)");
62
+ expect(blockFor("variable")).toContain("var(--code-variable, #e06c75)");
63
+ expect(blockFor("template-variable")).toContain("var(--code-variable, #e06c75)");
64
+ });
65
+
66
+ it("renders comments grey AND italic, as documented", () => {
67
+ const b = blockFor("comment");
68
+ expect(b).toContain("var(--code-comment, #5c6370)");
69
+ expect(b).toMatch(/font-style:\s*italic/);
70
+ });
71
+
72
+ it("does not paint backgrounds or the .hljs base — surfaces own those", () => {
73
+ expect(body).not.toMatch(/background[^;{]*:/);
74
+ expect(body).not.toMatch(/^\.hljs\s*\{/m);
75
+ // The diff classes belong to the diff surfaces, not the palette.
76
+ expect(body).not.toContain(".hljs-addition");
77
+ expect(body).not.toContain(".hljs-deletion");
78
+ });
79
+
80
+ it("is actually composed into the styles entry — a sheet nobody @uses is dead", () => {
81
+ const entry = readFileSync(
82
+ resolve(dirname(fileURLToPath(import.meta.url)), "index.scss"),
83
+ "utf-8",
84
+ );
85
+ expect(
86
+ entry.match(/@use\s+"\.\/theme\/[^"]+\.scss";/g) ?? [],
87
+ "positive control: the entry composes theme sheets",
88
+ ).toContain("@use \"./theme/channels.scss\";");
89
+ expect(entry).toContain('@use "./theme/highlight.scss";');
90
+ });
91
+ });
@@ -12,7 +12,10 @@
12
12
  // 3. theme/scrollbar.scss — the shared overlay-scrollbar chrome
13
13
  // (.hk-scrollbar-track/thumb) that attachOverlayScrollbars() attaches
14
14
  // to every scroll region (vendored from packages/theme/styles).
15
- // 4. admin-tokens.scss — the plana-ui shared surface: layout tokens,
15
+ // 4. theme/highlight.scss — the syntax palette: colours the .hljs-*
16
+ // spans useHighlight() emits, riding the --code-* family the host
17
+ // theme owns (vendored from packages/theme/styles).
18
+ // 5. admin-tokens.scss — the plana-ui shared surface: layout tokens,
16
19
  // brand-color aliases and the shared admin component classes.
17
20
  //
18
21
  // initTheme() (runtime) writes only deltas against this base into a
@@ -28,4 +31,5 @@
28
31
  @use "./theme/channels.scss";
29
32
  @use "./theme/scale.scss";
30
33
  @use "./theme/scrollbar.scss";
34
+ @use "./theme/highlight.scss";
31
35
  @use "./admin-tokens.scss";
@@ -0,0 +1,117 @@
1
+ // Vendored from packages/theme/styles/_highlight.scss so npm registry
2
+ // installs can resolve it — the exports map only exposes ./styles/*.
3
+ // This sheet is what COLOURS the `.hljs-*` spans that useHighlight() and
4
+ // any other highlight.js driver emit; without it the grammars load and
5
+ // the code still renders monochrome (infra without a palette).
6
+ // Edit the upstream file, then re-copy; do not hand-edit here.
7
+
8
+ // _highlight.scss — the syntax palette for highlight.js output.
9
+ //
10
+ // `useHighlight()` (and anything else driving highlight.js) emits
11
+ // `.hljs-*` spans; this sheet is what actually COLOURS them. Without it
12
+ // the grammars load, the spans land in the DOM, and the code stays
13
+ // monochrome — infra without a palette.
14
+ //
15
+ // Every colour rides the `--code-*` family documented in channels.scss
16
+ // (the family the host theme owns; extend it only by adding a token with
17
+ // a fallback equal to the current visual). The fallbacks here are the
18
+ // canonical One Dark values, so a standalone/themeless consumer renders
19
+ // like One Dark and a themed host (which defines `--code-*` in both
20
+ // modes) flips light/dark automatically — no `.hljs-*` rule anywhere in
21
+ // this sheet hardcodes a palette colour outside a var() fallback.
22
+ //
23
+ // The explicit pairs from the channels.scss doc block are canonical:
24
+ // keyword ← .hljs-keyword / .hljs-selector-tag
25
+ // builtin ← .hljs-built_in
26
+ // string ← .hljs-string
27
+ // number ← .hljs-number / .hljs-literal / .hljs-attr
28
+ // comment ← .hljs-comment / .hljs-doctag (grey, italic)
29
+ // function ← .hljs-title.function_
30
+ // type ← .hljs-type / class titles
31
+ // variable ← .hljs-variable / .hljs-template-variable
32
+ // The remaining highlight.js classes are bucketed to the nearest of the
33
+ // same eight by judgement (the canonical pairs above are exact); the
34
+ // goal is that every span lands in the family, not that each bucket
35
+ // matches One Dark's own file class-for-class.
36
+ //
37
+ // Deliberately NOT set: `.hljs` base colour/background and the
38
+ // `.hljs-addition` / `.hljs-deletion` diff classes — containers (diff
39
+ // panes, markdown fences) own their surfaces, and a palette layer that
40
+ // painted backgrounds would fight them.
41
+
42
+ // keyword — purple
43
+ .hljs-keyword,
44
+ .hljs-selector-tag,
45
+ .hljs-formula {
46
+ color: var(--code-keyword, #c678dd);
47
+ }
48
+
49
+ // builtin — purple in dark, blue in light
50
+ .hljs-built_in,
51
+ .hljs-selector-id,
52
+ .hljs-symbol,
53
+ .hljs-bullet,
54
+ .hljs-link,
55
+ .hljs-meta {
56
+ color: var(--code-builtin, #c678dd);
57
+ }
58
+
59
+ // string — green
60
+ .hljs-string,
61
+ .hljs-regexp,
62
+ .hljs-attribute,
63
+ .hljs-meta .hljs-string {
64
+ color: var(--code-string, #98c379);
65
+ }
66
+
67
+ // number — orange in dark, blue in light
68
+ .hljs-number,
69
+ .hljs-literal,
70
+ .hljs-attr,
71
+ .hljs-property,
72
+ .hljs-selector-class,
73
+ .hljs-selector-attr,
74
+ .hljs-selector-pseudo {
75
+ color: var(--code-number, #d19a66);
76
+ }
77
+
78
+ // comment — grey, italic
79
+ .hljs-comment,
80
+ .hljs-quote,
81
+ .hljs-doctag {
82
+ color: var(--code-comment, #5c6370);
83
+ font-style: italic;
84
+ }
85
+
86
+ // function — blue
87
+ .hljs-title,
88
+ .hljs-title.function_,
89
+ .hljs-title.function_.invoke__,
90
+ .hljs-section {
91
+ color: var(--code-function, #61afef);
92
+ }
93
+
94
+ // type — yellow in dark, green in light
95
+ .hljs-type,
96
+ .hljs-title.class_,
97
+ .hljs-class .hljs-title {
98
+ color: var(--code-type, #e5c07b);
99
+ }
100
+
101
+ // variable — red in dark, orange in light
102
+ .hljs-variable,
103
+ .hljs-template-variable,
104
+ .hljs-subst,
105
+ .hljs-name,
106
+ .hljs-tag {
107
+ color: var(--code-variable, #e06c75);
108
+ }
109
+
110
+ // Non-colour emphasis classes: keep the shapes highlight.js intends.
111
+ .hljs-emphasis {
112
+ font-style: italic;
113
+ }
114
+
115
+ .hljs-strong {
116
+ font-weight: bold;
117
+ }
@@ -52,6 +52,8 @@ const VENDORED_FROM_VUE: Array<[string, string]> = [
52
52
  // (packages/theme/styles) and is vendored here so npm consumers can
53
53
  // resolve it — the exports map only exposes ./styles/*.
54
54
  ["theme/scrollbar.scss", "../../theme/styles/_scrollbar.scss"],
55
+ // The syntax palette for .hljs-* spans lives there for the same reason.
56
+ ["theme/highlight.scss", "../../theme/styles/_highlight.scss"],
55
57
  ];
56
58
 
57
59