Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | 1x 10x 10x 15x 14x 10x 10x 10x 19x 19x 18x 18x 18x 14x 10x 6x 6x 6x 6x 9x 6x 4x 1x 3x 3x 3x 3x 3x 3x 4x 1x 1x | #!/usr/bin/env node
/**
* SLDS2 legacy hook audit.
*
* Walks the SLDS2 legacy Cosmos bundle (the pre-theme-layer output) and emits a
* JSON catalog that groups every `--slds-c-*` / `--_slds-c-*` hook by component
* prefix. The result is the authoritative record of what the legacy SLDS2 CSS
* exposed as a hook surface — useful for detecting customer hooks that came in
* through that bundle and need to be renormalized onto the current theme layer.
*
* Output shape mirrors slds1-hook-data.json:
* { _meta: { hookCount, componentCount, source }, <component>: [hook, ...] }
*
* Output path: build/legacy-hooks/slds2-legacy-hook-data.json. Consumed by the
* catalog build, which re-exports the JSON as `legacyHookData` on the catalog
* module.
*
* Run via `yarn build:legacy-hooks`.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { HOOK_RE } from './constants.js';
export const PREFIX_RE = /^--_?slds-c-([a-z][a-z0-9]*)-/;
// Pure: extract every distinct `--slds-c-*` hook from a CSS source. Skip
// context hooks (those are parent-set assignments, not the component's
// own surface). Returns a Set.
export function extractHooks(css) {
const hooks = new Set();
for (const [name] of css.matchAll(HOOK_RE)) {
if (name.includes('-context-')) continue;
hooks.add(name);
}
return hooks;
}
// Pure: group hooks by their component prefix (the first squashed-lowercase
// segment after `--slds-c-` or `--_slds-c-`). Returns Map<prefix, hook[]>
// with hook arrays alphabetically sorted.
export function groupByComponent(hooks) {
const out = new Map();
for (const hook of hooks) {
const m = hook.match(PREFIX_RE);
if (!m) continue;
const key = m[1];
if (!out.has(key)) out.set(key, []);
out.get(key).push(hook);
}
for (const arr of out.values()) arr.sort();
return out;
}
// Pure-ish: build the final JSON payload from CSS + metadata. The source path
// in `_meta` is normalized to be repo-relative by the caller (we accept it as
// an arg so tests can pass a stable string).
export function buildPayload({ css, sourceRelative, sourceMtime }) {
const hooks = extractHooks(css);
const byComponent = groupByComponent(hooks);
const sorted = [...byComponent.entries()].sort(([a], [b]) => a.localeCompare(b));
const out = {
_meta: {
// Record the source and its mtime instead of a wall-clock timestamp so
// the file only changes when the underlying bundle does.
hookCount: hooks.size,
componentCount: sorted.length,
source: {
path: sourceRelative,
mtime: sourceMtime,
},
},
};
for (const [comp, list] of sorted) out[comp] = list;
return out;
}
export function build({ source, outFile, pkgRoot }) {
if (!fs.existsSync(source)) {
throw new Error(`source not found: ${path.relative(pkgRoot, source)}`);
}
const stat = fs.statSync(source);
const css = fs.readFileSync(source, 'utf8');
const payload = buildPayload({
css,
sourceRelative: path.relative(pkgRoot, source),
sourceMtime: stat.mtime.toISOString(),
});
fs.mkdirSync(path.dirname(outFile), { recursive: true });
fs.writeFileSync(outFile, JSON.stringify(payload, null, 2) + '\n');
return payload;
}
export function defaultPaths(pkgRoot) {
return {
pkgRoot,
source: path.join(pkgRoot, 'dist/css/bundled/slds2.cosmos.css'),
outFile: path.join(pkgRoot, 'build/legacy-hooks/slds2-legacy-hook-data.json'),
};
}
// Only run when invoked as the entry module — keeps unit tests from triggering
// a real file write on import.
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
Iif (isMain) {
const paths = defaultPaths(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'));
// The legacy audit reads the bundled production CSS, which only
// `build:css:production` emits — `build:dev` produces modular CSS only. This
// step is wired into both build phases, so skip (not fail) when the bundle is
// absent: the dev phase has nothing to read, and the catalog build already
// treats the output as optional (falls back to `{}`). Production runs this
// after `build:css:production`, so the real data still gets written there.
if (!fs.existsSync(paths.source)) {
console.warn(
`[legacy-hooks] skipped: ${path.relative(paths.pkgRoot, paths.source)} not found ` +
'(only produced by `build:css:production`).',
);
process.exit(0);
}
try {
const payload = build(paths);
console.log(
`[legacy-hooks] wrote ${path.relative(paths.pkgRoot, paths.outFile)} (${payload._meta.hookCount} hooks, ${payload._meta.componentCount} components)`,
);
} catch (err) {
console.error(`[legacy-hooks] ${err.message}`);
process.exit(1);
}
}
|