diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 10a65ec4..32587a47 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,7 +17,8 @@ "lruihao.hugo-partials-refs", "tamasfe.even-better-toml", "casualjim.gotemplate", - "SomewhatStationery.some-sass" + "SomewhatStationery.some-sass", + "gruntfuggly.todo-tree" ] } } diff --git a/.github/workflows/gen-lexers.yml b/.github/workflows/gen-lexers.yml new file mode 100644 index 00000000..85433316 --- /dev/null +++ b/.github/workflows/gen-lexers.yml @@ -0,0 +1,39 @@ +name: Update Chroma lexer map +on: + schedule: + # Run at 00:00, on day 1 of the month + - cron: '0 0 1 * *' + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + update: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - uses: pnpm/action-setup@v6 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Generate lexer map + run: pnpm gen:lexers + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + title: 'chore(assets): regenerate chroma lexer map' + commit-message: 'chore(assets): regenerate chroma lexer map' + body: | + Auto-generated `assets/scss/core/maps/_chroma-lexers.scss` via `pnpm gen:lexers`. + Data from: [alecthomas/chroma](https://github.com/alecthomas/chroma/tree/master/lexers) latest release. + branch: auto/chroma-lexers + base: main + labels: dependencies + reviewers: Lruihao + author: Cell[bot] + committer: Cell[bot] diff --git a/CLAUDE.md b/CLAUDE.md index 3bc72b23..2aa2a6b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,12 @@ pnpm build # Build all sites (demo + test, merged into public/) pnpm preview # Preview built site (requires build first) ``` +### Code Generation + +```bash +pnpm gen:lexers # Regenerate assets/scss/core/maps/_chroma-lexers.scss from Chroma source +``` + ### Code Quality ```bash @@ -49,9 +55,10 @@ Root `package.json` is `@hugo-fixit/core`. pnpm workspaces include `apps/*` and - `apps/demo/` — Demo site (deployed to `demo.fixit.lruihao.cn`) - `apps/test/` — Test site for exercising theme features -- `packages/shared` — Shared utilities (exports `workspaceRoot`) +- `packages/shared` — Shared utilities (exports `workspaceRoot`, `capitalize`, etc.) - `packages/versioning` — Version management (auto-updates version in `layouts/_partials/init/index.html` during pre-commit) - `packages/integration` — Post-build: merges demo/test output into `public/` +- `packages/helpers` — Code generation helpers (CLI dispatcher pattern, sub-commands in `src//`) ### JavaScript Module System (`assets/js/`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 919f1f35..538a7648 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,6 +75,9 @@ pnpm lint # TypeScript type checking pnpm typecheck +# Regenerate Chroma lexer map (assets/scss/core/maps/_chroma-lexers.scss) +pnpm gen:lexers + # Preview the built site locally (requires build first) pnpm preview ``` @@ -120,7 +123,8 @@ FixIt/ ├── packages/ # Theme-related packages (pnpm workspaces) │ ├── shared/ # Shared utilities │ ├── versioning/ # Version management (pre-commit) -│ └── integration/ # Post-build site merging +│ ├── integration/ # Post-build site merging +│ └── helpers/ # Code generation helpers ├── static/ # Static files ├── hugo.toml # Default theme configuration └── package.json # npm scripts and dependencies diff --git a/assets/scss/core/functions/_var.scss b/assets/scss/core/functions/_var.scss index b8158eff..96927e13 100644 --- a/assets/scss/core/functions/_var.scss +++ b/assets/scss/core/functions/_var.scss @@ -1,3 +1,4 @@ +@use "sass:meta"; @use "sass:string"; @use "override" as *; @@ -10,6 +11,9 @@ /// background: fi-var(custom-bg, #fff); @function var($name, $fallback: null) { @if $fallback != null { + @if meta.type-of($fallback) == string { + @return string.unquote('var(#{$rootPrefix}#{$name}, "#{$fallback}")'); + } @return string.unquote("var(#{$rootPrefix}#{$name}, #{$fallback})"); } diff --git a/assets/scss/core/maps/_code-type.scss b/assets/scss/core/maps/_chroma-lexers.scss similarity index 89% rename from assets/scss/core/maps/_code-type.scss rename to assets/scss/core/maps/_chroma-lexers.scss index 3692df51..b722baa4 100644 --- a/assets/scss/core/maps/_code-type.scss +++ b/assets/scss/core/maps/_chroma-lexers.scss @@ -1,6 +1,8 @@ /// Map of code identifiers/languages to Chroma supported languages. -/// @generator https://github.com/hugo-fixit/.tools/blob/main/tampermonkey/code-type-map-fetcher.user.js -$code-type-map: ( +/// This file is auto-generated by `pnpm gen:lexers`, do not edit manually. +/// @see https://github.com/hugo-fixit/FixIt/tree/main/packages/helpers/src/chroma-lexers +$chroma-lexers: ( + "🔥": "Mojo", "1s": "OnesEnterprise", "1s\\:enterprise": "OnesEnterprise", "abap": "ABAP", @@ -16,6 +18,8 @@ $code-type-map: ( "ahk": "AutoHotkey", "al": "AL", "alloy": "Alloy", + "ampl": "AMPL", + "ansible": "YAML+Jinja", "antlr": "ANTLR", "apache": "ApacheConf", "apacheconf": "ApacheConf", @@ -25,6 +29,8 @@ $code-type-map: ( "arduino": "Arduino", "arexx": "Rexx", "armasm": "ArmAsm", + "art": "Arturo", + "arturo": "Arturo", "as": "ActionScript", "as3": "ActionScript 3", "asm": "GAS", @@ -54,6 +60,7 @@ $code-type-map: ( "c": "C", "c\\#": "C#", "c\\+\\+": "C++", + "c3": "C3", "caddy": "Caddyfile", "caddy-d": "Caddyfile Directives", "caddyfile": "Caddyfile", @@ -101,6 +108,7 @@ $code-type-map: ( "dax": "Dax", "desktop": "Desktop file", "desktop_entry": "Desktop file", + "devicetree": "Devicetree", "diff": "Diff", "django": "Django/Jinja", "docker": "Docker", @@ -108,6 +116,7 @@ $code-type-map: ( "dosbatch": "Batchfile", "dosini": "INI", "dtd": "DTD", + "dts": "Devicetree", "duby": "Ruby", "dylan": "Dylan", "ebnf": "EBNF", @@ -117,6 +126,7 @@ $code-type-map: ( "elm": "Elm", "emacs": "EmacsLisp", "emacs-lisp": "EmacsLisp", + "erb": "ERB", "erlang": "Erlang", "ex": "Elixir", "exs": "Elixir", @@ -136,6 +146,8 @@ $code-type-map: ( "gd3": "GDScript3", "gdscript": "GDScript", "gdscript3": "GDScript3", + "gemfile-lock": "Gemfile.lock", + "gemfilelock": "Gemfile.lock", "gemini": "Gemtext", "gemtext": "Gemtext", "genshi": "Genshi", @@ -147,9 +159,7 @@ $code-type-map: ( "gmni": "Gemtext", "gnuplot": "Gnuplot", "go": "Go", - "go-html-template": "Go HTML Template", "go-template": "Go Template", - "go-text-template": "Go Text Template", "golang": "Go", "gql": "GraphQL", "graphql": "GraphQL", @@ -162,15 +172,17 @@ $code-type-map: ( "haskell": "Haskell", "haxe": "Haxe", "hbs": "Handlebars", - "hcl": "Terraform", + "hcl": "HCL", "hexdump": "Hexdump", "hlb": "HLB", "hlsl": "HLSL", "holyc": "HolyC", "hs": "Haskell", "html": "HTML", + "html\\+erb": "ERB", "html\\+genshi": "Genshi HTML", "html\\+kid": "Genshi HTML", + "html\\+ruby": "ERB", "http": "HTTP", "hx": "Haxe", "hxsl": "Haxe", @@ -192,22 +204,31 @@ $code-type-map: ( "js": "JavaScript", "json": "JSON", "jsonata": "JSONata", + "jsonl": "JSON", "jsonnet": "Jsonnet", "jsx": "React", "julia": "Julia", "jungle": "Jungle", + "kak": "Kakoune", + "kakoune": "Kakoune", + "kakrc": "Kakoune", + "kakscript": "Kakoune", + "kdl": "KDL", "kid": "Genshi", "kotlin": "Kotlin", "ksh": "Bash", + "lateralus": "Lateralus", "latex": "TeX", "lean": "Lean4", "lean4": "Lean4", "lighttpd": "Lighttpd configuration file", "lighty": "Lighttpd configuration file", + "lilypond": "LilyPond", "lisp": "Common Lisp", "llvm": "LLVM", + "ltl": "Lateralus", "lua": "Lua", - "luau": "Lua", + "luau": "Luau", "m2": "Modula-2", "make": "Makefile", "makefile": "Makefile", @@ -219,21 +240,25 @@ $code-type-map: ( "mathematica": "Mathematica", "matlab": "Matlab", "mawk": "Awk", + "mbt": "MoonBit", "mcf": "MCFunction", "mcfunction": "MCFunction", "md": "Markdown", "meson": "Meson", "meson\\.build": "Meson", + "mess": "Markless", "metal": "Metal", "mf": "Makefile", "minizinc": "MiniZinc", "mkd": "Markdown", "mlir": "MLIR", "mma": "Mathematica", + "modelica": "Modelica", "modula2": "Modula-2", "mojo": "Mojo", "monkeyc": "MonkeyC", "moon": "MoonScript", + "moonbit": "MoonBit", "moonscript": "MoonScript", "morrowind": "MorrowindScript", "mwscript": "MorrowindScript", @@ -290,12 +315,14 @@ $code-type-map: ( "plc": "Plutus Core", "plpgsql": "PL/pgSQL", "plutus-core": "Plutus Core", + "po": "Gettext", "pony": "Pony", "posh": "PowerShell", "postgres": "PostgreSQL SQL dialect", "postgresql": "PostgreSQL SQL dialect", "postscr": "PostScript", "postscript": "PostScript", + "pot": "Gettext", "pov": "POVRay", "powerquery": "PowerQuery", "powershell": "PowerShell", @@ -337,6 +364,9 @@ $code-type-map: ( "rest": "ReStructuredText", "restructuredtext": "ReStructuredText", "rexx": "Rexx", + "rgbasm": "RGBDS Assembly", + "rhtml": "ERB", + "ring": "Ring", "rkt": "Racket", "rs": "Rust", "rst": "ReStructuredText", @@ -344,9 +374,11 @@ $code-type-map: ( "rust": "Rust", "s": "R", "sage": "Python", + "salt": "YAML+Jinja", "sas": "SAS", "sass": "Sass", "scala": "Scala", + "scdoc": "Scdoc", "scheme": "Scheme", "scilab": "Scilab", "scm": "Scheme", @@ -356,6 +388,7 @@ $code-type-map: ( "shell": "Bash", "shell-session": "Bash Session", "sieve": "Sieve", + "sls": "YAML+Jinja", "smali": "Smali", "smalltalk": "Smalltalk", "smarty": "Smarty", @@ -365,6 +398,7 @@ $code-type-map: ( "sol": "Solidity", "solidity": "Solidity", "sp": "SourcePawn", + "spade": "Spade", "sparql": "SPARQL", "spec": "RPMSpec", "spitfire": "Cheetah", @@ -377,6 +411,7 @@ $code-type-map: ( "squidconf": "SquidConf", "ssed": "Sed", "st": "Smalltalk", + "starlark": "Python", "stylus": "Stylus", "sv": "Systemverilog", "svelte": "Svelte", @@ -406,6 +441,7 @@ $code-type-map: ( "turtle": "Turtle", "tv": "TradingView", "twig": "Twig", + "txtpb": "Protocol Buffer Text Format", "typescript": "TypeScript", "typoscript": "TypoScript", "typoscriptcssdata": "TypoScriptCssData", @@ -413,7 +449,7 @@ $code-type-map: ( "typst": "Typst", "udiff": "Diff", "uxntal": "Tal", - "v": "Verilog", + "v": "V", "vala": "Vala", "vapi": "Vala", "vb\\.net": "VB.net", @@ -428,6 +464,8 @@ $code-type-map: ( "vtt": "WebVTT", "vue": "Vue", "vuejs": "Vue", + "wast": "WebAssembly Text Format", + "wat": "WebAssembly Text Format", "wgsl": "WebGPU Shading Language", "whiley": "Whiley", "winbatch": "Batchfile", @@ -436,13 +474,14 @@ $code-type-map: ( "xml\\+kid": "Genshi", "xorg\\.conf": "Xorg", "yaml": "YAML", + "yaml\\+jinja": "YAML+Jinja", "yang": "YANG", "z80": "Z80 Assembly", "zed": "Zed", "zig": "Zig", "zone": "Dns", "zsh": "Bash", - "🔥": "Mojo", + "µcad": "Microcad", // Custom code type "markdown": "Markdown", "yml": "YAML" diff --git a/assets/scss/core/maps/_index.scss b/assets/scss/core/maps/_index.scss index aea25988..e328a966 100644 --- a/assets/scss/core/maps/_index.scss +++ b/assets/scss/core/maps/_index.scss @@ -1,5 +1,5 @@ @forward 'admonition'; @forward 'alert'; -@forward 'code-type'; +@forward 'chroma-lexers'; @forward 'colors'; @forward 'utilities'; diff --git a/assets/scss/core/mixins/_theme-vars.scss b/assets/scss/core/mixins/_theme-vars.scss index bc7b2a1c..5200f2b1 100644 --- a/assets/scss/core/mixins/_theme-vars.scss +++ b/assets/scss/core/mixins/_theme-vars.scss @@ -11,7 +11,7 @@ /// @example /// @include set-fi-var(custom-color, #ff0000); @mixin set-fi-var($name, $value) { - #{$rootPrefix}#{$name}: #{$value}; + #{$rootPrefix}#{$name}: $value; } /// Batch set CSS variables @@ -23,7 +23,7 @@ /// )); @mixin set-fi-vars($vars) { @each $name, $value in $vars { - #{$rootPrefix}#{$name}: #{$value}; + #{$rootPrefix}#{$name}: $value; } } diff --git a/assets/scss/pages/single/_code.scss b/assets/scss/pages/single/_code.scss index c65c93bd..9e846663 100644 --- a/assets/scss/pages/single/_code.scss +++ b/assets/scss/pages/single/_code.scss @@ -205,7 +205,7 @@ @include set-fi-var(code-expand-offset, 1.875rem + 0.5rem); } } - + .code-header { display: flex; justify-content: space-between; @@ -290,10 +290,10 @@ color: fi-var(global-link-hover-color); } - @each $type, $text in $code-type-map { + @each $type, $text in $chroma-lexers { &.language-#{string.to-lower-case($type)} { // e.g., --fi-code-type: "JavaScript"; - @include set-fi-var(code-type, '"#{$text}"'); + @include set-fi-var(code-type, $text); } } diff --git a/package.json b/package.json index ce603818..56b9711e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "typecheck": "tsc --noEmit", "version": "pnpm -F versioning start prod", "changelog": "auto-changelog-plus --starting-date 2021-12-18", + "gen:lexers": "pnpm -F helpers start chroma-lexers", "prepare": "simple-git-hooks" }, "devDependencies": { diff --git a/packages/helpers/package.json b/packages/helpers/package.json new file mode 100644 index 00000000..c2a5cb3f --- /dev/null +++ b/packages/helpers/package.json @@ -0,0 +1,15 @@ +{ + "name": "@hugo-fixit/helpers", + "private": true, + "description": "Code generation helpers for the FixIt theme", + "author": "Lruihao", + "main": "src/index.ts", + "scripts": { + "start": "tsx src/index.ts" + }, + "devDependencies": { + "@hugo-fixit/shared": "workspace:*", + "consola": "^3.4.2", + "ora": "^9.4.0" + } +} diff --git a/packages/helpers/src/chroma-lexers/custom-types.ts b/packages/helpers/src/chroma-lexers/custom-types.ts new file mode 100644 index 00000000..7c40424b --- /dev/null +++ b/packages/helpers/src/chroma-lexers/custom-types.ts @@ -0,0 +1,5 @@ +/** Custom code type entries not present in Chroma's embedded lexers. */ +export const customCodeTypes: Record = { + markdown: 'Markdown', + yml: 'YAML', +} diff --git a/packages/helpers/src/chroma-lexers/fetch-lexers.ts b/packages/helpers/src/chroma-lexers/fetch-lexers.ts new file mode 100644 index 00000000..cafa7ab3 --- /dev/null +++ b/packages/helpers/src/chroma-lexers/fetch-lexers.ts @@ -0,0 +1,201 @@ +import { Buffer } from 'node:buffer' +import { execSync } from 'node:child_process' +import process from 'node:process' +import consola from 'consola' +import ora from 'ora' + +export interface LexerEntry { + name: string + aliases: string[] +} + +interface GitHubContentEntry { + name: string + type: string + sha: string +} + +interface GitHubBlobResponse { + content: string + encoding: string +} + +const CONCURRENCY = 10 + +async function getLatestChromaRef(): Promise { + if (process.env.CHROMA_REF) { + return process.env.CHROMA_REF + } + try { + const res = await fetch('https://api.github.com/repos/alecthomas/chroma/releases/latest', { + headers: getHeaders(), + }) + if (res.ok) { + const data = await res.json() as { tag_name: string } + return data.tag_name + } + } + catch {} + return 'v2.24.1' +} + +function getGitHubToken(): string | undefined { + if (process.env.GITHUB_TOKEN) { + return process.env.GITHUB_TOKEN + } + try { + return execSync('gh auth token', { encoding: 'utf-8' }).trim() + } + catch { + return undefined + } +} + +function getHeaders(): Record { + const headers: Record = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': '@hugo-fixit/helpers', + } + const token = getGitHubToken() + if (token) { + headers.Authorization = `Bearer ${token}` + } + return headers +} + +async function fetchBlobContent(sha: string): Promise { + const url = `https://api.github.com/repos/alecthomas/chroma/git/blobs/${sha}` + const res = await fetch(url, { headers: getHeaders() }) + if (!res.ok) { + throw new Error(`HTTP ${res.status} for blob ${sha}`) + } + const data: GitHubBlobResponse = await res.json() as GitHubBlobResponse + if (data.encoding === 'base64') { + return Buffer.from(data.content, 'base64').toString('utf-8') + } + return data.content +} + +async function fetchDirectoryListing(dir: string, ref: string): Promise> { + const url = `https://api.github.com/repos/alecthomas/chroma/contents/${dir}?ref=${ref}&per_page=300` + const res = await fetch(url, { headers: getHeaders() }) + if (!res.ok) { + throw new Error(`Failed to fetch directory listing for ${dir}: HTTP ${res.status}`) + } + const entries: GitHubContentEntry[] = await res.json() as GitHubContentEntry[] + return entries + .filter(e => e.type === 'file') + .map(e => ({ name: e.name, sha: e.sha })) +} + +function decodeHtmlEntities(str: string): string { + return str + .replace(/'/g, '\'') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') +} + +function parseLexerXml(xml: string): LexerEntry[] { + const nameMatch = xml.match(/(.*?)<\/name>/) + if (!nameMatch) + return [] + + const name = decodeHtmlEntities(nameMatch[1]) + const aliases: string[] = [] + const aliasRegex = /(.*?)<\/alias>/g + for (let match = aliasRegex.exec(xml); match !== null; match = aliasRegex.exec(xml)) { + aliases.push(match[1]) + } + + return [{ name, aliases }] +} + +function parseLexerGo(content: string): LexerEntry[] { + const results: LexerEntry[] = [] + // Split by MustNewLexer to handle each lexer definition separately + const blocks = content.split('MustNewLexer(') + + for (const block of blocks) { + const nameMatch = block.match(/Name:\s*"([^"]+)"/) + if (!nameMatch) + continue + + const name = nameMatch[1] + const aliasesMatch = block.match(/Aliases:\s*\[\]string\{([^}]*)\}/) + const aliases = aliasesMatch + ? aliasesMatch[1] + .split(',') + .map(s => s.trim().replace(/^"|"$/g, '')) + .filter(s => s.length > 0) + : [] + + results.push({ name, aliases }) + } + + return results +} + +async function processBatch( + files: Array<{ name: string, sha: string }>, + parser: (content: string) => LexerEntry[], +): Promise { + const results: LexerEntry[] = [] + const failed: string[] = [] + + await Promise.all( + files.map(async ({ name, sha }) => { + try { + const content = await fetchBlobContent(sha) + const entries = parser(content) + results.push(...entries) + } + catch { + failed.push(name) + } + }), + ) + + if (failed.length > 0) { + consola.warn(`Warning: failed to fetch ${failed.length} file(s): ${failed.join(', ')}`) + } + + return results +} + +export async function fetchLexers(): Promise { + const ref = await getLatestChromaRef() + consola.info(`Loading lexer definitions from chroma@${ref}`) + const spinner = ora('Fetching lexers...').start() + + // Fetch XML-defined lexers from embedded/ directory + const xmlFiles = (await fetchDirectoryListing('lexers/embedded', ref)) + .filter(f => f.name.endsWith('.xml')) + + // Fetch Go-implemented lexers from lexers/ directory + const goFiles = (await fetchDirectoryListing('lexers', ref)) + .filter(f => f.name.endsWith('.go') && !f.name.endsWith('_test.go') && f.name !== 'lexers.go') + + const total = xmlFiles.length + goFiles.length + const results: LexerEntry[] = [] + let fetched = 0 + + for (let i = 0; i < xmlFiles.length; i += CONCURRENCY) { + const batch = xmlFiles.slice(i, i + CONCURRENCY) + results.push(...await processBatch(batch, parseLexerXml)) + fetched += batch.length + spinner.text = `Fetching lexers... (${fetched}/${total})` + } + + for (let i = 0; i < goFiles.length; i += CONCURRENCY) { + const batch = goFiles.slice(i, i + CONCURRENCY) + results.push(...await processBatch(batch, parseLexerGo)) + fetched += batch.length + spinner.text = `Fetching lexers... (${fetched}/${total})` + } + + results.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) + spinner.succeed(`Fetched ${results.length} lexers from ${xmlFiles.length} XML + ${goFiles.length} Go files.`) + return results +} diff --git a/packages/helpers/src/chroma-lexers/generate-scss.ts b/packages/helpers/src/chroma-lexers/generate-scss.ts new file mode 100644 index 00000000..a9985841 --- /dev/null +++ b/packages/helpers/src/chroma-lexers/generate-scss.ts @@ -0,0 +1,56 @@ +import type { LexerEntry } from './fetch-lexers' +import fs from 'node:fs' +import path from 'node:path' +import { capitalize, workspaceRoot } from '@hugo-fixit/shared' +import consola from 'consola' +import { customCodeTypes } from './custom-types' + +function escapeScssAlias(alias: string): string { + // Match the old Tampermonkey script: replace . + : # with \\ + char + return alias.replace(/([+.:#])/g, '\\\\$1') +} + +export function generateScss(lexers: LexerEntry[]): void { + const aliasMap = new Map() + + for (const lexer of lexers) { + for (const alias of lexer.aliases) { + if (alias.includes(' ') || alias === '') + continue + const key = alias.toLowerCase() + if (!aliasMap.has(key)) { + aliasMap.set(key, capitalize(lexer.name)) + } + } + } + + const sorted = [...aliasMap.entries()].sort((a, b) => + a[0].localeCompare(b[0]), + ) + + const lines: string[] = [ + '/// Map of code identifiers/languages to Chroma supported languages.', + '/// This file is auto-generated by `pnpm gen:lexers`, do not edit manually.', + '/// @see https://github.com/hugo-fixit/FixIt/tree/main/packages/helpers/src/chroma-lexers', + '$chroma-lexers: (', + ] + + for (const [alias, name] of sorted) { + lines.push(` "${escapeScssAlias(alias)}": "${name}",`) + } + + lines.push(' // Custom code type') + + const customEntries = Object.entries(customCodeTypes) + for (let i = 0; i < customEntries.length; i++) { + const [key, value] = customEntries[i] + const comma = i < customEntries.length - 1 ? ',' : '' + lines.push(` "${key}": "${value}"${comma}`) + } + + lines.push(') !default;') + + const outputPath = path.join(workspaceRoot, 'assets/scss/core/maps/_chroma-lexers.scss') + fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf-8') + consola.success(`Written ${sorted.length + customEntries.length} entries to ${path.relative(workspaceRoot, outputPath)}`) +} diff --git a/packages/helpers/src/chroma-lexers/index.ts b/packages/helpers/src/chroma-lexers/index.ts new file mode 100644 index 00000000..98926fa9 --- /dev/null +++ b/packages/helpers/src/chroma-lexers/index.ts @@ -0,0 +1,7 @@ +import { fetchLexers } from './fetch-lexers' +import { generateScss } from './generate-scss' + +export default async function main() { + const lexers = await fetchLexers() + generateScss(lexers) +} diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts new file mode 100644 index 00000000..32614542 --- /dev/null +++ b/packages/helpers/src/index.ts @@ -0,0 +1,21 @@ +import process from 'node:process' +import consola from 'consola' + +async function main() { + const helper = process.argv[2] + if (!helper) { + consola.error('Usage: pnpm -F helpers start ') + process.exit(1) + } + + const mod = await import(`./${helper}/index.ts`).catch(() => { + consola.error(`Unknown helper: ${helper}`) + process.exit(1) + }) + + if (typeof mod.default === 'function') { + await mod.default() + } +} + +main() diff --git a/packages/helpers/tsconfig.json b/packages/helpers/tsconfig.json new file mode 100644 index 00000000..e8d13465 --- /dev/null +++ b/packages/helpers/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext"], + "module": "nodenext", + "moduleResolution": "nodenext", + "resolveJsonModule": true, + "types": ["node"], + "strict": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 018c57c9..a95e301d 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,5 @@ import path from 'node:path' export const workspaceRoot = path.resolve(__dirname, '../../../') + +export * from './utils' diff --git a/packages/shared/src/utils/capitalize.ts b/packages/shared/src/utils/capitalize.ts new file mode 100644 index 00000000..8d5e54db --- /dev/null +++ b/packages/shared/src/utils/capitalize.ts @@ -0,0 +1,3 @@ +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1) +} diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts new file mode 100644 index 00000000..ebb8c4f8 --- /dev/null +++ b/packages/shared/src/utils/index.ts @@ -0,0 +1 @@ +export * from './capitalize' diff --git a/packages/versioning/package.json b/packages/versioning/package.json index de5cc971..a278329a 100644 --- a/packages/versioning/package.json +++ b/packages/versioning/package.json @@ -8,6 +8,7 @@ "start": "tsx src/index.ts" }, "devDependencies": { - "@hugo-fixit/shared": "workspace:*" + "@hugo-fixit/shared": "workspace:*", + "consola": "^3.4.2" } } diff --git a/packages/versioning/src/index.ts b/packages/versioning/src/index.ts index b5554d9d..d924854b 100644 --- a/packages/versioning/src/index.ts +++ b/packages/versioning/src/index.ts @@ -1,9 +1,10 @@ import process from 'node:process' +import consola from 'consola' import { updateVersion } from './update-version' const type: string = process.argv[2] if (type !== 'dev' && type !== 'prod') { - console.error('Invalid argument. Please specify "dev" or "prod".') + consola.error('Invalid argument. Please specify "dev" or "prod".') process.exit(1) } diff --git a/packages/versioning/src/update-version.ts b/packages/versioning/src/update-version.ts index d1973fe7..ba666860 100644 --- a/packages/versioning/src/update-version.ts +++ b/packages/versioning/src/update-version.ts @@ -1,9 +1,9 @@ -/* eslint-disable no-console */ import { execFileSync, execSync } from 'node:child_process' import fs from 'node:fs' import { join } from 'node:path' import process from 'node:process' import { workspaceRoot } from '@hugo-fixit/shared' +import consola from 'consola' const ISO_TIMESTAMP_CLEAN_RE = /[-:TZ]/g const VERSION_PATCH_RE = /(\d+)$/ @@ -30,20 +30,20 @@ export function updateVersion(type: 'dev' | 'prod') { ] const gitDiff: string = execSync('git diff --cached --name-only').toString().trim() - // console.log( - // 'Node.js:', - // execSync('which node').toString().trim(), - // process.version, - // ) + consola.info( + 'Node.js:', + execSync('which node').toString().trim(), + process.version, + ) if (type !== 'prod') { // Avoid conflicts when creating a Pull Request if (!['dev', 'main'].includes(branch)) { - console.log(`The current branch is ${branch}, no need to update the FixIt version.`) + consola.info(`The current branch is ${branch}, no need to update the FixIt version.`) process.exit(0) } if (!match.some(item => gitDiff.includes(item))) { - console.log('No need to update the FixIt version.') + consola.info('No need to update the FixIt version.') process.exit(0) } } @@ -71,7 +71,7 @@ export function updateVersion(type: 'dev' | 'prod') { if (lastVersion === version && gitDiff.includes('layouts/_partials/init/index.html')) { // After running `npm version` or manually modifying the version number, skip the update - console.log(`The FixIt version has been updated to v${lastVersion}.`) + consola.info(`The FixIt version has been updated to v${lastVersion}.`) process.exit(0) } @@ -81,7 +81,7 @@ export function updateVersion(type: 'dev' | 'prod') { if (type === 'prod' && nextVersion) { delete packageJson.nextVersion fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`) - console.log('Removed nextVersion field from package.json.') + consola.info('Removed nextVersion field from package.json.') } // Add the updated files to the git stage const toStageFiles: string[] = [ @@ -96,5 +96,5 @@ export function updateVersion(type: 'dev' | 'prod') { execFileSync('git', ['add', stageFile]) } }) - console.log(`Update the FixIt version from v${lastVersion} to v${latestVersion}.`) + consola.info(`Update the FixIt version from v${lastVersion} to v${latestVersion}.`) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd061019..b06b2323 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,18 @@ importers: apps/test: {} + packages/helpers: + devDependencies: + '@hugo-fixit/shared': + specifier: workspace:* + version: link:../shared + consola: + specifier: ^3.4.2 + version: 3.4.2 + ora: + specifier: ^9.4.0 + version: 9.4.0 + packages/integration: devDependencies: '@hugo-fixit/shared': @@ -62,6 +74,9 @@ importers: '@hugo-fixit/shared': specifier: workspace:* version: link:../shared + consola: + specifier: ^3.4.2 + version: 3.4.2 packages: @@ -740,6 +755,10 @@ packages: resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -762,6 +781,10 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-truncate@5.2.0: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} @@ -819,6 +842,10 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -1305,6 +1332,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-port-reachable@4.0.0: resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1313,6 +1344,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -1383,6 +1418,10 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -1618,6 +1657,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@9.4.0: + resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} + engines: {node: '>=20'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1831,6 +1874,10 @@ packages: spdx-license-ids@3.0.23: resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2053,6 +2100,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2713,6 +2764,8 @@ snapshots: chalk@5.0.1: {} + chalk@5.6.2: {} + change-case@5.4.4: {} character-entities@2.0.2: {} @@ -2729,6 +2782,8 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-spinners@3.4.0: {} + cli-truncate@5.2.0: dependencies: slice-ansi: 8.0.0 @@ -2791,6 +2846,8 @@ snapshots: confbox@0.2.4: {} + consola@3.4.2: {} + content-disposition@0.5.2: {} core-js-compat@3.49.0: @@ -3304,10 +3361,14 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-interactive@2.0.0: {} + is-port-reachable@4.0.0: {} is-stream@2.0.1: {} + is-unicode-supported@2.1.0: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -3382,6 +3443,11 @@ snapshots: lodash.merge@4.6.2: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + log-update@6.1.0: dependencies: ansi-escapes: 7.3.0 @@ -3809,6 +3875,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@9.4.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.1 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4007,6 +4084,8 @@ snapshots: spdx-license-ids@3.0.23: {} + stdin-discarder@0.3.2: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -4226,4 +4305,6 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + zwitch@2.0.4: {}