Feature 05 task 1: Design tokens and self-hosted fonts (#32)
CI / ci (push) Successful in 1m29s
CI / ci (push) Successful in 1m29s
Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com> Co-committed-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #32.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Fails when the production build references Google Fonts: fonts are
|
||||
// self-hosted, so dist must never point at fonts.googleapis.com or
|
||||
// fonts.gstatic.com.
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { join, relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const distDir = fileURLToPath(new URL('../dist', import.meta.url))
|
||||
const hosts = ['fonts.googleapis.com', 'fonts.gstatic.com']
|
||||
|
||||
function* files(dir) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name)
|
||||
if (entry.isDirectory()) yield* files(path)
|
||||
else yield path
|
||||
}
|
||||
}
|
||||
|
||||
const violations = []
|
||||
for (const path of files(distDir)) {
|
||||
const content = readFileSync(path, 'latin1')
|
||||
for (const host of hosts) {
|
||||
if (content.includes(host)) violations.push(`${relative(distDir, path)}: ${host}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error('Production build references Google Fonts; fonts must be self-hosted:')
|
||||
for (const v of violations) console.error(` ${v}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('check:dist-fonts: dist makes no Google Fonts requests')
|
||||
@@ -0,0 +1,42 @@
|
||||
// Fails when a hex colour literal appears in any CSS or TypeScript file under
|
||||
// web/ outside the design tokens: every colour must come from a custom
|
||||
// property in tokens.css. Dependencies, build output and hidden directories
|
||||
// (tool caches) are not source and are skipped.
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { extname, join, relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const webRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||
const allowed = new Set(['src/styles/tokens.css'])
|
||||
const skippedDirs = new Set([join(webRoot, 'node_modules'), join(webRoot, 'dist')])
|
||||
const extensions = new Set(['.css', '.ts', '.tsx'])
|
||||
const hexColour = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g
|
||||
|
||||
function* sourceFiles(dir) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (!skippedDirs.has(path) && !entry.name.startsWith('.')) yield* sourceFiles(path)
|
||||
} else if (extensions.has(extname(entry.name))) yield path
|
||||
}
|
||||
}
|
||||
|
||||
const violations = []
|
||||
for (const path of sourceFiles(webRoot)) {
|
||||
const name = relative(webRoot, path).split('\\').join('/')
|
||||
if (allowed.has(name)) continue
|
||||
readFileSync(path, 'utf8')
|
||||
.split('\n')
|
||||
.forEach((line, i) => {
|
||||
for (const match of line.matchAll(hexColour)) {
|
||||
violations.push(`${name}:${i + 1}: ${match[0]}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error('Hex colour literals found; use a token from src/styles/tokens.css:')
|
||||
for (const v of violations) console.error(` ${v}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('lint:colors: no hex colour literals outside src/styles/tokens.css')
|
||||
Reference in New Issue
Block a user