Skip to content

Commit bbdc0e4

Browse files
committed
chore: harden the semantic-widening against formatting and export drift
Review follow-up on #5239, round two. - The widened selector is matched with whitespace-tolerant regexes and emitted in Prettier's canonical one-selector-per-line form, shared between the widen script and the bundler via semantic-scope.mjs, so a prettier --write no longer breaks the pipeline with a misleading 'export layout changed' error. - Every semantic/*.css file is widened and asserted, mirroring the bundler's directory glob - a file the export adds later cannot slip through with the narrow :root selector. - src/tokens/* is added to .prettierignore (root and package-local): the directory is machine output and was never Prettier-conformant (the export emits double-quoted attribute selectors and no trailing newline, failing prettier:check on main too), and hand-formatting it would be reverted by the next export run. The package prettier:check now passes.
1 parent 374083d commit bbdc0e4

7 files changed

Lines changed: 71 additions & 36 deletions

File tree

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@
22
README.md
33
packages/eds-icons/src/data.ts
44
packages/eds-tokens/build/*
5+
# Generated by the Tokens Studio release pipeline — machine output,
6+
# never hand-formatted (a prettier --write would be reverted by the
7+
# next export run anyway)
8+
packages/eds-tokens/src/tokens/*
59
packages/eds-icons/src/data.ts
610
packages/eds-core-react/src/components/Select/NativeSelect.tsx
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Generated by the Tokens Studio release pipeline — machine output,
2+
# never hand-formatted (a prettier --write would be reverted by the
3+
# next export run anyway). Mirrored in the repo-root .prettierignore;
4+
# this copy covers the package-local prettier:check / --write runs,
5+
# which resolve ignore files from the package directory.
6+
src/tokens/*

packages/eds-tokens/scripts/generate-css-bundle.mjs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@
3535
*/
3636
import { execFileSync } from 'node:child_process'
3737
import { readFile, readdir, writeFile } from 'node:fs/promises'
38-
import { join, relative, resolve } from 'node:path'
38+
import { join, relative, resolve, sep } from 'node:path'
3939
import process from 'node:process'
4040
import { fileURLToPath } from 'node:url'
41+
import { WIDE_RE } from './semantic-scope.mjs'
4142

4243
const args = parseArgs(process.argv.slice(2))
4344
const CSS_DIR = args.css ?? 'src/tokens/css'
@@ -57,23 +58,24 @@ const files = (await readdir(CSS_DIR, { recursive: true }))
5758

5859
if (files.length === 0) fail(`no CSS files found under ${CSS_DIR}`)
5960

61+
const contents = await Promise.all(
62+
files.map(async (file) => [file, await readFile(file, 'utf8')]),
63+
)
64+
6065
// The widen-semantic-scope.mjs step must have run first (it is chained
6166
// before this script in the `generate:css-bundle` package script) —
6267
// bundling an unwidened semantic layer would silently regress subtree
63-
// colour-scheme switching (#5226). Must match WIDE in that script.
64-
const SEMANTIC_FILE = join(CSS_DIR, 'semantic', 'default.css')
65-
const WIDENED = ':root, [data-color-scheme] {'
66-
const semantic = await readFile(SEMANTIC_FILE, 'utf8').catch(() =>
67-
fail(`cannot read ${SEMANTIC_FILE}`),
68-
)
69-
if (!semantic.startsWith(WIDENED))
70-
fail(
71-
`${SEMANTIC_FILE} does not start with "${WIDENED}" — run scripts/widen-semantic-scope.mjs before bundling (or use the generate:css-bundle package script, which chains it)`,
72-
)
68+
// colour-scheme switching (#5226). Checked for every semantic/*.css
69+
// file, mirroring the widen script's own glob.
70+
for (const [file, css] of contents) {
71+
if (relative(CSS_DIR, file).split(sep)[0] !== 'semantic') continue
72+
if (!WIDE_RE.test(css))
73+
fail(
74+
`${file} is not widened to ":root, [data-color-scheme]" — run scripts/widen-semantic-scope.mjs before bundling (or use the generate:css-bundle package script, which chains it)`,
75+
)
76+
}
7377

74-
const concatenated = (
75-
await Promise.all(files.map((file) => readFile(file, 'utf8')))
76-
).join('\n')
78+
const concatenated = contents.map(([, css]) => css).join('\n')
7779

7880
await writeFile(OUT_FILE, HEADER + concatenated)
7981

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Shared definition of the widened semantic-layer selector (#5226),
3+
* used by widen-semantic-scope.mjs (writes it) and
4+
* generate-css-bundle.mjs (asserts it before bundling).
5+
*
6+
* WIDE is the Prettier-canonical form (one selector per line) so the
7+
* generated files are stable under `prettier --write` / formatOnSave.
8+
* The regexes are whitespace-tolerant for the same reason — matching
9+
* must not depend on which tool touched the file last.
10+
*/
11+
export const WIDE = ':root,\n[data-color-scheme] {'
12+
export const WIDE_RE = /^:root,\s*\[data-color-scheme\]\s*\{/
13+
export const NARROW_RE = /^:root\s*\{/

packages/eds-tokens/scripts/widen-semantic-scope.mjs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
* element clobbers a `[data-density]` ancestor's values for the whole
1919
* subtree. The export format has no per-layer selector, hence this
2020
* post-export step. It runs before generate-css-bundle.mjs (chained in
21-
* the `generate:css-bundle` package script).
21+
* the `generate:css-bundle` package script), and the bundler asserts
22+
* the widening happened (shared pattern in semantic-scope.mjs).
2223
*
2324
* Known caveat, tracked in #5221: three names (`border-focus`,
2425
* `text-disabled`, `border-disabled`) are declared in both the
@@ -27,33 +28,40 @@
2728
* (it sorts last in the bundle) — a token-content bug upstream, not a
2829
* consequence of this step.
2930
*
30-
* Idempotent; fails loudly if the semantic file is missing or does not
31-
* start with the expected selector.
31+
* Every `semantic/*.css` file is widened, matching the bundler's
32+
* directory glob — a file the export adds later must not slip through
33+
* with the narrow selector. Idempotent; fails loudly if a semantic
34+
* file does not start with either the narrow or the widened selector.
3235
*
33-
* Usage: node scripts/widen-semantic-scope.mjs [--file <path>]
36+
* Usage: node scripts/widen-semantic-scope.mjs [--dir <path>]
3437
*/
35-
import { readFile, writeFile } from 'node:fs/promises'
38+
import { readFile, readdir, writeFile } from 'node:fs/promises'
39+
import { join } from 'node:path'
3640
import process from 'node:process'
41+
import { NARROW_RE, WIDE, WIDE_RE } from './semantic-scope.mjs'
3742

3843
const args = parseArgs(process.argv.slice(2))
39-
const FILE = args.file ?? 'src/tokens/css/semantic/default.css'
44+
const DIR = args.dir ?? 'src/tokens/css/semantic'
4045

41-
const NARROW = ':root {'
42-
const WIDE = ':root, [data-color-scheme] {'
46+
const files = (await readdir(DIR).catch(() => fail(`cannot read ${DIR}`)))
47+
.filter((file) => file.endsWith('.css'))
48+
.sort()
4349

44-
const css = await readFile(FILE, 'utf8').catch(() =>
45-
fail(`cannot read ${FILE}`),
46-
)
50+
if (files.length === 0) fail(`no CSS files found under ${DIR}`)
4751

48-
if (css.startsWith(WIDE)) {
49-
console.log(`widen-semantic-scope: ${FILE} already widened`)
50-
} else if (css.startsWith(NARROW)) {
51-
await writeFile(FILE, WIDE + css.slice(NARROW.length))
52-
console.log(`widen-semantic-scope: widened ${FILE} to "${WIDE.slice(0, -2)}"`)
53-
} else {
54-
fail(
55-
`${FILE} does not start with "${NARROW}" — the export layout changed, review #5226 before proceeding`,
56-
)
52+
for (const file of files) {
53+
const path = join(DIR, file)
54+
const css = await readFile(path, 'utf8')
55+
if (WIDE_RE.test(css)) {
56+
console.log(`widen-semantic-scope: ${path} already widened`)
57+
} else if (NARROW_RE.test(css)) {
58+
await writeFile(path, css.replace(NARROW_RE, WIDE))
59+
console.log(`widen-semantic-scope: widened ${path}`)
60+
} else {
61+
fail(
62+
`${path} does not start with a ":root" selector — the export layout changed, review #5226 before proceeding`,
63+
)
64+
}
5765
}
5866

5967
function parseArgs(argv) {

packages/eds-tokens/src/tokens/css/semantic/default.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
:root, [data-color-scheme] {
1+
:root,
2+
[data-color-scheme] {
23
--eds-background-container-canvas-default: var(--eds-neutral-1);
34
--eds-background-container-card-default: var(--eds-neutral-15);
45
--eds-background-container-popover-default: var(--eds-neutral-15);

packages/eds-tokens/src/tokens/css/variables.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,8 @@
655655
--eds-primitives-weight-scale-lighter: 300;
656656
--eds-primitives-weight-scale-normal: 400;
657657
}
658-
:root, [data-color-scheme] {
658+
:root,
659+
[data-color-scheme] {
659660
--eds-background-container-canvas-default: var(--eds-neutral-1);
660661
--eds-background-container-card-default: var(--eds-neutral-15);
661662
--eds-background-container-popover-default: var(--eds-neutral-15);

0 commit comments

Comments
 (0)