ReviewOS

also looking at this

loghqorg/loghq

chore(release): promote main to production

#19
Merged glennmichael123 wants to merge main into production
8 files +325 -31

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

tests/unit/no-vanilla-js.test.tsadded+88-0
Changes to tests/unit/no-vanilla-js.test.ts
@@ -0,0 +1,88 @@
1/**
2 * Rule 8 (stx-standards 08-no-vanilla-js): no vanilla DOM in a client script.
3 *
4 * config/ui.ts turns stx's strict mode on, but that is only a partial gate.
5 * The renderer validates whatever reaches validateClientScript, and a
6 * signal-using <script client> block is re-tagged data-stx-scoped by
7 * processScriptSetup and then skipped by process.js's own skipAttrs -- so the
8 * blocks most likely to touch the DOM are the ones it never sees. It also
9 * only ever checks pages something actually rendered, which makes a
10 * dev-server boot a sampling exercise rather than a check.
11 *
12 * This reads the source instead, where no block has been re-tagged yet, so it
13 * sees every one of them. It calls stx's own exported validator rather than
14 * re-listing the prohibited patterns, so the rules cannot drift from the
15 * engine's, and honours `stx-strict-ignore` markers because that validator
16 * parses them itself.
17 */
18import { describe, expect, it } from 'bun:test'
19import { readdirSync, readFileSync, statSync } from 'node:fs'
20import { join, relative } from 'node:path'
21import { validateClientScript } from '@stacksjs/stx'
22
23const root = process.cwd()
24
25/** Vendored or generated; their violations are not this repo's to fix. */
26const SKIP = new Set(['node_modules', 'storage', 'pantry', '.git', 'dist', 'public', 'coverage'])
27
28/**
29 * Not a client script: server blocks, external sources, and any explicit
30 * non-JS type. Mirrors the skipAttrs process.js passes its scanner, minus the
31 * two render-time markers (data-stx-scoped, data-stx-router) that by
32 * definition cannot be in a source file -- excluding them here is what makes
33 * this broader than the renderer.
34 */
35const NOT_CLIENT = /\bserver\b|\bsrc\s*=|\btype\s*=\s*["'](?!(?:text\/javascript|application\/javascript|module)["'])[^"']*["']/
36const SCRIPT = /<script\b([^>]*)>([\s\S]*?)<\/script>/g
37
38function stxFiles(dir: string): string[] {
39 let found: string[] = []
40 for (const name of readdirSync(dir)) {
41 if (SKIP.has(name) || name.startsWith('.') && name !== '.stx') continue
42 const path = join(dir, name)
43 let entry
44 try { entry = statSync(path) }
45 catch { continue }
46 if (entry.isDirectory()) found = found.concat(stxFiles(path))
47 else if (path.endsWith('.stx')) found.push(path)
48 }
49 return found
50}
51
52function scan(): { blocks: number, files: number, violations: string[] } {
53 const violations: string[] = []
54 const files = stxFiles(root)
55 let blocks = 0
56 for (const file of files) {
57 const name = relative(root, file)
58 for (const [, attrs, body] of readFileSync(file, 'utf8').matchAll(SCRIPT)) {
59 if (NOT_CLIENT.test(attrs ?? '')) continue
60 blocks++
61 // failOnViolation rather than reading console.warn: the validator
62 // memoises per (content, file, config) and a remembered verdict
63 // re-throws but does not warn again, so two identical blocks would
64 // otherwise report once.
65 try { validateClientScript(body ?? '', name, { enabled: true, failOnViolation: true, allowPatterns: [] }, name) }
66 catch (error) { violations.push(error instanceof Error ? error.message : String(error)) }
67 }
68 }
69 return { blocks, files: files.length, violations }
70}
71
72const result = scan()
73
74describe('rule 8: no vanilla JS in client scripts', () => {
75 it('reaches the client scripts it is meant to check', () => {
76 // Without this the suite would pass just as happily on a scan that
77 // matched nothing at all, which is the one failure mode a green test
78 // cannot tell you about.
79 expect(result.files).toBeGreaterThan(0)
80 expect(result.blocks).toBeGreaterThan(0)
81 })
82
83 it('finds no prohibited DOM calls', () => {
84 // Asserting on the joined report rather than a count: the diff on failure
85 // is the list of offending files, lines and suggested composables.
86 expect(result.violations.join('\n\n')).toBe('')
87 })
88})