Repository navigation
Expand file tree
/
Copy pathcheck-cli-theme-bundle.test.mjs
More file actions
135 lines (121 loc) · 4.74 KB
/
Copy pathcheck-cli-theme-bundle.test.mjs
File metadata and controls
135 lines (121 loc) · 4.74 KB
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
134
135
// Copyright (c) Meta Platforms, Inc. and affiliates.
/**
* @file Drift guard for the bundled CLI themes.
*
* `scripts/generate-cli-themes.mjs` copies each theme's source verbatim into
* `packages/cli/assets/templates/themes/` so `astryx theme add` can scaffold a
* theme without the package installed. Nothing verified the bundle stayed in
* sync with source, so the neutral theme's WCAG text-secondary fix (and a
* StatusDot override block) shipped to source but never reached the bundle —
* `astryx theme add neutral` scaffolded a theme below AA contrast. This test
* pins the copies to their source byte-for-byte; when a theme changes, run
* `pnpm bundle:cli-themes` and commit the regenerated bundle.
*/
import fs from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {describe, it, expect} from 'vitest';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const THEMES_SRC_ROOT = path.join(REPO_ROOT, 'packages', 'themes');
const CLI_THEMES_OUT = path.join(
REPO_ROOT,
'packages',
'cli',
'assets',
'templates',
'themes',
);
const toIdentifier = (slug) =>
slug.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
const readJSON = (file) => JSON.parse(fs.readFileSync(file, 'utf-8'));
/** Theme slugs discovered the same way the generator discovers them. */
function themeSlugs() {
if (!fs.existsSync(THEMES_SRC_ROOT)) return [];
return fs
.readdirSync(THEMES_SRC_ROOT, {withFileTypes: true})
.filter((e) => e.isDirectory())
.map((e) => e.name)
.filter((slug) => {
const themeFile = path.join(
THEMES_SRC_ROOT,
slug,
'src',
`${toIdentifier(slug)}Theme.ts`,
);
if (!fs.existsSync(themeFile)) return false;
// Private packages are not themes a user can pick — packages/themes/probe
// is a generated visual-gate fixture. The generator skips them, so this
// discovery must too, or it would demand the fixture be bundled.
const pkg = path.join(THEMES_SRC_ROOT, slug, 'package.json');
return !fs.existsSync(pkg) || readJSON(pkg).private !== true;
})
.sort();
}
/** Theme dirs that exist but must never reach the CLI tarball. */
function privateThemeSlugs() {
if (!fs.existsSync(THEMES_SRC_ROOT)) return [];
return fs
.readdirSync(THEMES_SRC_ROOT, {withFileTypes: true})
.filter((e) => e.isDirectory())
.map((e) => e.name)
.filter((slug) => {
const pkg = path.join(THEMES_SRC_ROOT, slug, 'package.json');
return fs.existsSync(pkg) && readJSON(pkg).private === true;
})
.sort();
}
describe('CLI theme bundle is in sync with source', () => {
const slugs = themeSlugs();
it('discovers at least one theme to check', () => {
expect(slugs.length).toBeGreaterThan(0);
});
for (const slug of slugs) {
const id = toIdentifier(slug);
const themeFileName = `${id}Theme.ts`;
const src = path.join(THEMES_SRC_ROOT, slug, 'src', themeFileName);
const bundled = path.join(CLI_THEMES_OUT, slug, themeFileName);
it(`${slug}: bundled ${themeFileName} matches source (run \`pnpm bundle:cli-themes\`)`, () => {
expect(
fs.existsSync(bundled),
`missing bundled theme: ${path.relative(REPO_ROOT, bundled)}`,
).toBe(true);
expect(fs.readFileSync(bundled, 'utf8')).toBe(
fs.readFileSync(src, 'utf8'),
);
});
const srcIcons = path.join(THEMES_SRC_ROOT, slug, 'src', 'icons.tsx');
if (fs.existsSync(srcIcons)) {
it(`${slug}: bundled icons.tsx matches source`, () => {
const bundledIcons = path.join(CLI_THEMES_OUT, slug, 'icons.tsx');
expect(
fs.existsSync(bundledIcons),
`missing bundled icons: ${path.relative(REPO_ROOT, bundledIcons)}`,
).toBe(true);
expect(fs.readFileSync(bundledIcons, 'utf8')).toBe(
fs.readFileSync(srcIcons, 'utf8'),
);
});
}
}
// These assets ship inside the CLI tarball, so a private package that
// happens to live in packages/themes becomes an installable theme unless
// the generator excludes it. The probe fixture did exactly that once.
for (const slug of privateThemeSlugs()) {
it(`${slug}: private theme package is not shipped to CLI users`, () => {
expect(
fs.existsSync(path.join(CLI_THEMES_OUT, slug)),
`packages/themes/${slug} is private but is bundled into the CLI — ` +
'it would be offered by `astryx theme add`',
).toBe(false);
});
}
it('bundles exactly the discovered themes, and nothing else', () => {
const bundled = fs
.readdirSync(CLI_THEMES_OUT, {withFileTypes: true})
.filter((e) => e.isDirectory())
.map((e) => e.name)
.sort();
expect(bundled).toEqual(slugs);
});
});