@pathscale/rsbuild-plugin-ui-css-purge 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @pathscale/rebuild-plugin-ui-css-purge
2
+
3
+ Three-level CSS purge for `@pathscale/ui` consumers. Analyzes JSX usage at build time, cross-references with a component class manifest, and strips unused CSS rules, attribute selectors, and custom properties.
4
+
5
+ Runs as a postbuild step under Bun. Zero Node dependencies.
6
+
7
+ ## How it works
8
+
9
+ The purge operates in two phases across two repositories:
10
+
11
+ **Phase 1 (lib-side, `@pathscale/ui`):** Each component ships a `.classnames.ts` file declaring all CSS classes it uses, organized by slot (`base`, `variant`, `size`, `flag`, `color`, `attrs`). A prebuild script reads these files and produces `purge-manifest.json` — a compact database mapping components to their class and attribute requirements.
12
+
13
+ **Phase 2 (consumer-side, e.g. `honey.id`):** After `rsbuild build`, the postbuild script scans the consumer's JSX source with [swc](https://swc.rs/), finds all `@pathscale/ui` component usages with their prop values, and cross-references with the manifest to determine exactly which CSS classes and attribute selectors are needed. Three purge levels run in sequence:
14
+
15
+ | Level | What it does | Engine |
16
+ |-------|-------------|--------|
17
+ | L1 | Removes entire CSS rules whose class selectors aren't in the safelist | [purgecss](https://purgecss.com/) |
18
+ | L2 | Within kept rules, strips `[data-*]` / `[aria-*]` attribute selectors not in the attr safelist | [postcss](https://postcss.org/) AST walk |
19
+ | L3 | Iteratively removes CSS custom properties that are declared but never referenced | postcss AST walk |
20
+
21
+ ## Results
22
+
23
+ Tested on `honey.id` with 3 components having `.classnames.ts` files (Button, Breadcrumbs, Navbar):
24
+
25
+ ```
26
+ 440.7 KB raw CSS (before)
27
+ 42.3 KB after L1 (class purge)
28
+ 42.3 KB after L2 (attr purge)
29
+ 27.7 KB after L3 (var cleanup)
30
+ 4.4 KB brotli compressed
31
+ ```
32
+
33
+ 93.7% reduction in raw CSS size.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ bun add -d @pathscale/rebuild-plugin-ui-css-purge
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ### Consumer project (postbuild purge)
44
+
45
+ Add to your build script in `package.json`:
46
+
47
+ ```json
48
+ {
49
+ "scripts": {
50
+ "build": "rsbuild build && bunx rebuild-plugin-ui-css-purge --manifest node_modules/@pathscale/ui/dist/purge-manifest.json"
51
+ }
52
+ }
53
+ ```
54
+
55
+ Options:
56
+
57
+ | Flag | Default | Description |
58
+ |------|---------|-------------|
59
+ | `--manifest` | (required) | Path to `purge-manifest.json` |
60
+ | `--dist` | `./dist` | Directory containing built CSS files |
61
+ | `--src` | `./src` | Consumer source directory to scan for JSX usage |
62
+
63
+ ### Lib-side (manifest generation)
64
+
65
+ Run from `@pathscale/ui` as a prebuild step:
66
+
67
+ ```json
68
+ {
69
+ "scripts": {
70
+ "prebuild": "bunx generate-manifest src/components --out dist/purge-manifest.json"
71
+ }
72
+ }
73
+ ```
74
+
75
+ This scans all `*.classnames.ts` files and produces the manifest that consumers use.
76
+
77
+ ## The `.classnames.ts` convention
78
+
79
+ Every component in `@pathscale/ui` gets a sibling `.classnames.ts` file exporting a `CLASSES` const. The component imports it and references every class through `CLASSES.*`. This makes static analysis trivial — no JSX parsing needed to know which classes a component can produce.
80
+
81
+ ```ts
82
+ // Button.classnames.ts
83
+ export const CLASSES = {
84
+ base: "inline-flex items-center justify-center rounded-md font-medium",
85
+ variant: {
86
+ primary: "bg-primary text-white",
87
+ secondary: "bg-secondary text-white",
88
+ ghost: "bg-transparent",
89
+ },
90
+ size: {
91
+ sm: "h-8 px-3 text-sm",
92
+ md: "h-10 px-4 text-base",
93
+ lg: "h-12 px-6 text-lg",
94
+ },
95
+ flag: {
96
+ isDisabled: "opacity-50 cursor-not-allowed",
97
+ },
98
+ } as const;
99
+ ```
100
+
101
+ **Slots:**
102
+
103
+ | Slot | Shape | Purpose |
104
+ |------|-------|---------|
105
+ | `base` | `string \| string[]` | Always rendered when the component mounts |
106
+ | `variant`, `size`, `color` | `{ enumValue: classString }` | Enum prop value maps to classes |
107
+ | `flag` | `{ propName: classString }` | Boolean prop name maps to classes |
108
+ | `attrs` | `{ propName: { attr: value } }` | L2 attribute selectors tied to props |
109
+
110
+ Compound components use a nested shape: `CLASSES = { Root: { base, ... }, Item: { base, ... } }`.
111
+
112
+ ## Programmatic API
113
+
114
+ The scanner and safelist builder are also exported for custom integrations:
115
+
116
+ ```ts
117
+ import { scanConsumerSource, buildSafelists } from "@pathscale/rebuild-plugin-ui-css-purge";
118
+
119
+ const usages = await scanConsumerSource("/path/to/consumer/src");
120
+ const manifest = JSON.parse(await Bun.file("purge-manifest.json").text());
121
+ const { classSafelist, attrSafelist } = buildSafelists(usages, manifest);
122
+ ```
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ bun install
128
+ bun run build # dist/index.js + dist/postbuild-purge.js + type declarations
129
+ bun run lint
130
+ bun run format
131
+ ```
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lib-side database generator.
3
+ *
4
+ * Reads all `*.classes.ts` files from @pathscale/ui's component tree
5
+ * and produces a `purge-manifest.json` that the consumer-side plugin uses
6
+ * to build safelists.
7
+ *
8
+ * Usage: bun run src/generate-manifest.ts <path-to-ui-src/components>
9
+ * Output: purge-manifest.json in cwd (or pass --out <path>)
10
+ */
11
+ export {};
@@ -0,0 +1,2 @@
1
+ export { extractUIImports, extractJSXUsages, buildSafelists, scanConsumerSource } from "./scan-consumer";
2
+ export type { PropUsage, PurgeManifest, ComponentManifest, Safelists } from "./scan-consumer";
package/dist/index.js ADDED
@@ -0,0 +1,255 @@
1
+ // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = import.meta.require;
34
+
35
+ // src/scan-consumer.ts
36
+ import swc from "@swc/core";
37
+ var {Glob } = globalThis.Bun;
38
+ import path from "path";
39
+ function walkAST(node, visitor) {
40
+ if (!node || typeof node !== "object")
41
+ return;
42
+ visitor(node);
43
+ for (const key of Object.keys(node)) {
44
+ if (key === "span")
45
+ continue;
46
+ const val = node[key];
47
+ if (Array.isArray(val)) {
48
+ for (const item of val)
49
+ walkAST(item, visitor);
50
+ } else if (val && typeof val === "object") {
51
+ walkAST(val, visitor);
52
+ }
53
+ }
54
+ }
55
+ function extractUIImports(ast) {
56
+ const imports = new Map;
57
+ for (const node of ast.body) {
58
+ if (node.type !== "ImportDeclaration")
59
+ continue;
60
+ const src = node.source?.value;
61
+ if (!src || !src.startsWith("@pathscale/ui"))
62
+ continue;
63
+ for (const spec of node.specifiers) {
64
+ if (spec.type === "ImportSpecifier") {
65
+ const imported = spec.imported?.value ?? spec.local?.value;
66
+ const local = spec.local?.value;
67
+ if (local && imported)
68
+ imports.set(local, imported);
69
+ } else if (spec.type === "ImportDefaultSpecifier") {
70
+ const local = spec.local?.value;
71
+ if (local)
72
+ imports.set(local, local);
73
+ }
74
+ }
75
+ }
76
+ return imports;
77
+ }
78
+ function extractJSXUsages(ast, uiComponents) {
79
+ const usages = [];
80
+ walkAST(ast, (node) => {
81
+ if (node.type !== "JSXOpeningElement")
82
+ return;
83
+ let elementName = null;
84
+ let rootName = null;
85
+ if (node.name?.type === "Identifier") {
86
+ elementName = node.name.value;
87
+ rootName = elementName;
88
+ } else if (node.name?.type === "JSXMemberExpression") {
89
+ const parts = [];
90
+ let cursor = node.name;
91
+ while (cursor?.type === "JSXMemberExpression") {
92
+ parts.unshift(cursor.property?.value);
93
+ cursor = cursor.object;
94
+ }
95
+ if (cursor?.type === "Identifier") {
96
+ parts.unshift(cursor.value);
97
+ rootName = cursor.value;
98
+ }
99
+ elementName = parts.join(".");
100
+ }
101
+ if (!rootName || !uiComponents.has(rootName))
102
+ return;
103
+ const usage = {
104
+ component: elementName,
105
+ props: new Map,
106
+ booleanProps: new Set,
107
+ hasSpread: false
108
+ };
109
+ for (const attr of node.attributes || []) {
110
+ if (attr.type === "SpreadElement" || attr.type === "JSXSpreadAttribute") {
111
+ usage.hasSpread = true;
112
+ continue;
113
+ }
114
+ if (attr.type !== "JSXAttribute")
115
+ continue;
116
+ const propName = attr.name?.value;
117
+ if (!propName)
118
+ continue;
119
+ if (!attr.value) {
120
+ usage.booleanProps.add(propName);
121
+ } else if (attr.value.type === "StringLiteral") {
122
+ usage.props.set(propName, attr.value.value);
123
+ } else {
124
+ usage.props.set(propName, "DYNAMIC");
125
+ }
126
+ }
127
+ usages.push(usage);
128
+ });
129
+ return usages;
130
+ }
131
+ function buildSafelists(allUsages, manifest) {
132
+ const classSafelist = new Set;
133
+ const attrSafelist = new Set;
134
+ const componentUsages = new Map;
135
+ for (const usage of allUsages) {
136
+ const existing = componentUsages.get(usage.component) ?? [];
137
+ existing.push(usage);
138
+ componentUsages.set(usage.component, existing);
139
+ }
140
+ for (const [entryName, entry] of Object.entries(manifest)) {
141
+ const matchingUsages = findMatchingUsages(entryName, componentUsages);
142
+ if (matchingUsages.length === 0) {
143
+ continue;
144
+ }
145
+ for (const cls of entry.classes.always) {
146
+ classSafelist.add(cls);
147
+ }
148
+ for (const [propOrSlot, value] of Object.entries(entry.classes.byProp)) {
149
+ if (Array.isArray(value)) {
150
+ if (isPropUsed(propOrSlot, matchingUsages)) {
151
+ for (const cls of value)
152
+ classSafelist.add(cls);
153
+ }
154
+ } else {
155
+ const usedValues = getUsedEnumValues(propOrSlot, matchingUsages);
156
+ if (usedValues === "ALL") {
157
+ for (const classes of Object.values(value)) {
158
+ for (const cls of classes)
159
+ classSafelist.add(cls);
160
+ }
161
+ } else {
162
+ for (const val of usedValues) {
163
+ if (value[val]) {
164
+ for (const cls of value[val])
165
+ classSafelist.add(cls);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ if (entry.attrs) {
172
+ for (const [propName, attrMap] of Object.entries(entry.attrs)) {
173
+ if (isPropUsed(propName, matchingUsages)) {
174
+ for (const [attr, val] of Object.entries(attrMap)) {
175
+ attrSafelist.add(`${attr}=${val}`);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return { classSafelist, attrSafelist };
182
+ }
183
+ function findMatchingUsages(entryName, usageMap) {
184
+ if (usageMap.has(entryName)) {
185
+ return usageMap.get(entryName);
186
+ }
187
+ const results = [];
188
+ for (const [usageName, usages] of usageMap) {
189
+ if (usageName === entryName) {
190
+ results.push(...usages);
191
+ }
192
+ if (entryName.includes(".")) {
193
+ const [family, part] = entryName.split(".");
194
+ if (part === family && usageName === family) {
195
+ results.push(...usages);
196
+ }
197
+ }
198
+ }
199
+ return results;
200
+ }
201
+ function isPropUsed(propName, usages) {
202
+ for (const usage of usages) {
203
+ if (usage.hasSpread)
204
+ return true;
205
+ if (usage.booleanProps.has(propName))
206
+ return true;
207
+ if (usage.props.has(propName))
208
+ return true;
209
+ }
210
+ return false;
211
+ }
212
+ function getUsedEnumValues(slotName, usages) {
213
+ const values = new Set;
214
+ for (const usage of usages) {
215
+ if (usage.hasSpread)
216
+ return "ALL";
217
+ const val = usage.props.get(slotName);
218
+ if (val === "DYNAMIC")
219
+ return "ALL";
220
+ if (val !== undefined)
221
+ values.add(val);
222
+ if (usage.booleanProps.has(slotName))
223
+ return "ALL";
224
+ }
225
+ return values;
226
+ }
227
+ async function scanConsumerSource(srcDir) {
228
+ const allUsages = [];
229
+ const glob = new Glob("**/*.{tsx,ts,jsx,js}");
230
+ for await (const relPath of glob.scan({ cwd: srcDir })) {
231
+ if (relPath.includes("node_modules"))
232
+ continue;
233
+ const fullPath = path.join(srcDir, relPath);
234
+ const code = await Bun.file(fullPath).text();
235
+ if (!code.includes("@pathscale/ui"))
236
+ continue;
237
+ const isTsx = /\.[tj]sx$/.test(relPath);
238
+ const ast = await swc.parse(code, { syntax: "typescript", tsx: isTsx });
239
+ const uiImports = extractUIImports(ast);
240
+ if (uiImports.size === 0)
241
+ continue;
242
+ allUsages.push(...extractJSXUsages(ast, uiImports));
243
+ }
244
+ return allUsages;
245
+ }
246
+ if (false) {}
247
+ export {
248
+ scanConsumerSource,
249
+ extractUIImports,
250
+ extractJSXUsages,
251
+ buildSafelists
252
+ };
253
+
254
+ //# debugId=550954AA443AD8F064756E2164756E21
255
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3NjYW4tY29uc3VtZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbCiAgICAiLyoqXG4gKiBDb25zdW1lci1zaWRlIEpTWCBzY2FubmVyLlxuICpcbiAqIFdhbGtzIGEgY29uc3VtZXIncyBzb3VyY2UgdHJlZSwgZmluZHMgY29tcG9uZW50IGltcG9ydHMgZnJvbSBAcGF0aHNjYWxlL3VpLFxuICogY29sbGVjdHMgcHJvcCB2YWx1ZXMsIGFuZCBjcm9zcy1yZWZlcmVuY2VzIHdpdGggdGhlIHB1cmdlIG1hbmlmZXN0IHRvIGJ1aWxkXG4gKiBMZXZlbCAxIChjbGFzcykgYW5kIExldmVsIDIgKGF0dHJpYnV0ZSkgc2FmZWxpc3RzLlxuICpcbiAqIFVzYWdlOiAgYnVuIHJ1biBzcmMvc2Nhbi1jb25zdW1lci50cyA8Y29uc3VtZXItc3JjLWRpcj4gPHB1cmdlLW1hbmlmZXN0Lmpzb24+XG4gKi9cblxuaW1wb3J0IHN3YyBmcm9tIFwiQHN3Yy9jb3JlXCI7XG5pbXBvcnQgeyBHbG9iIH0gZnJvbSBcImJ1blwiO1xuaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcblxuLy8g4pSA4pSAIFR5cGVzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5pbnRlcmZhY2UgQ29tcG9uZW50TWFuaWZlc3Qge1xuICBjbGFzc2VzOiB7XG4gICAgYWx3YXlzOiBzdHJpbmdbXTtcbiAgICBieVByb3A6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdIHwgUmVjb3JkPHN0cmluZywgc3RyaW5nW10+PjtcbiAgfTtcbiAgYXR0cnM/OiBSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+PjtcbiAgZGVwcz86IHN0cmluZ1tdO1xufVxuXG50eXBlIFB1cmdlTWFuaWZlc3QgPSBSZWNvcmQ8c3RyaW5nLCBDb21wb25lbnRNYW5pZmVzdD47XG5cbi8qKiBXaGF0IHdlIGNvbGxlY3QgcGVyIGNvbXBvbmVudCB1c2FnZSBmcm9tIEpTWCAqL1xuaW50ZXJmYWNlIFByb3BVc2FnZSB7XG4gIGNvbXBvbmVudDogc3RyaW5nO1xuICBwcm9wczogTWFwPHN0cmluZywgc3RyaW5nIHwgXCJEWU5BTUlDXCI+OyAvLyBwcm9wTmFtZSDihpIgbGl0ZXJhbCB2YWx1ZSBvciBEWU5BTUlDXG4gIGJvb2xlYW5Qcm9wczogU2V0PHN0cmluZz47IC8vIHByb3BzIHByZXNlbnQgd2l0aG91dCBhIHZhbHVlICh0cnV0aHkpXG4gIGhhc1NwcmVhZDogYm9vbGVhbjtcbn1cblxuLy8g4pSA4pSAIEFTVCB3YWxrZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmZ1bmN0aW9uIHdhbGtBU1Qobm9kZTogYW55LCB2aXNpdG9yOiAobm9kZTogYW55KSA9PiB2b2lkKSB7XG4gIGlmICghbm9kZSB8fCB0eXBlb2Ygbm9kZSAhPT0gXCJvYmplY3RcIikgcmV0dXJuO1xuICB2aXNpdG9yKG5vZGUpO1xuICBmb3IgKGNvbnN0IGtleSBvZiBPYmplY3Qua2V5cyhub2RlKSkge1xuICAgIGlmIChrZXkgPT09IFwic3BhblwiKSBjb250aW51ZTtcbiAgICBjb25zdCB2YWwgPSBub2RlW2tleV07XG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsKSkge1xuICAgICAgZm9yIChjb25zdCBpdGVtIG9mIHZhbCkgd2Fsa0FTVChpdGVtLCB2aXNpdG9yKTtcbiAgICB9IGVsc2UgaWYgKHZhbCAmJiB0eXBlb2YgdmFsID09PSBcIm9iamVjdFwiKSB7XG4gICAgICB3YWxrQVNUKHZhbCwgdmlzaXRvcik7XG4gICAgfVxuICB9XG59XG5cbi8qKiBFeHRyYWN0IEBwYXRoc2NhbGUvdWkgaW1wb3J0cyBmcm9tIGEgcGFyc2VkIG1vZHVsZSAqL1xuZnVuY3Rpb24gZXh0cmFjdFVJSW1wb3J0cyhhc3Q6IGFueSk6IE1hcDxzdHJpbmcsIHN0cmluZz4ge1xuICBjb25zdCBpbXBvcnRzID0gbmV3IE1hcDxzdHJpbmcsIHN0cmluZz4oKTtcbiAgZm9yIChjb25zdCBub2RlIG9mIGFzdC5ib2R5KSB7XG4gICAgaWYgKG5vZGUudHlwZSAhPT0gXCJJbXBvcnREZWNsYXJhdGlvblwiKSBjb250aW51ZTtcbiAgICBjb25zdCBzcmMgPSBub2RlLnNvdXJjZT8udmFsdWUgYXMgc3RyaW5nO1xuICAgIGlmICghc3JjIHx8ICFzcmMuc3RhcnRzV2l0aChcIkBwYXRoc2NhbGUvdWlcIikpIGNvbnRpbnVlO1xuXG4gICAgZm9yIChjb25zdCBzcGVjIG9mIG5vZGUuc3BlY2lmaWVycykge1xuICAgICAgaWYgKHNwZWMudHlwZSA9PT0gXCJJbXBvcnRTcGVjaWZpZXJcIikge1xuICAgICAgICBjb25zdCBpbXBvcnRlZCA9IHNwZWMuaW1wb3J0ZWQ/LnZhbHVlID8/IHNwZWMubG9jYWw/LnZhbHVlO1xuICAgICAgICBjb25zdCBsb2NhbCA9IHNwZWMubG9jYWw/LnZhbHVlO1xuICAgICAgICBpZiAobG9jYWwgJiYgaW1wb3J0ZWQpIGltcG9ydHMuc2V0KGxvY2FsLCBpbXBvcnRlZCk7XG4gICAgICB9IGVsc2UgaWYgKHNwZWMudHlwZSA9PT0gXCJJbXBvcnREZWZhdWx0U3BlY2lmaWVyXCIpIHtcbiAgICAgICAgY29uc3QgbG9jYWwgPSBzcGVjLmxvY2FsPy52YWx1ZTtcbiAgICAgICAgaWYgKGxvY2FsKSBpbXBvcnRzLnNldChsb2NhbCwgbG9jYWwpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gaW1wb3J0cztcbn1cblxuLyoqIEV4dHJhY3QgSlNYIHVzYWdlcyBvZiBVSSBjb21wb25lbnRzICovXG5mdW5jdGlvbiBleHRyYWN0SlNYVXNhZ2VzKGFzdDogYW55LCB1aUNvbXBvbmVudHM6IE1hcDxzdHJpbmcsIHN0cmluZz4pOiBQcm9wVXNhZ2VbXSB7XG4gIGNvbnN0IHVzYWdlczogUHJvcFVzYWdlW10gPSBbXTtcblxuICB3YWxrQVNUKGFzdCwgKG5vZGUpID0+IHtcbiAgICBpZiAobm9kZS50eXBlICE9PSBcIkpTWE9wZW5pbmdFbGVtZW50XCIpIHJldHVybjtcblxuICAgIGxldCBlbGVtZW50TmFtZTogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gICAgbGV0IHJvb3ROYW1lOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcblxuICAgIGlmIChub2RlLm5hbWU/LnR5cGUgPT09IFwiSWRlbnRpZmllclwiKSB7XG4gICAgICBlbGVtZW50TmFtZSA9IG5vZGUubmFtZS52YWx1ZTtcbiAgICAgIHJvb3ROYW1lID0gZWxlbWVudE5hbWU7XG4gICAgfSBlbHNlIGlmIChub2RlLm5hbWU/LnR5cGUgPT09IFwiSlNYTWVtYmVyRXhwcmVzc2lvblwiKSB7XG4gICAgICBjb25zdCBwYXJ0czogc3RyaW5nW10gPSBbXTtcbiAgICAgIGxldCBjdXJzb3IgPSBub2RlLm5hbWU7XG4gICAgICB3aGlsZSAoY3Vyc29yPy50eXBlID09PSBcIkpTWE1lbWJlckV4cHJlc3Npb25cIikge1xuICAgICAgICBwYXJ0cy51bnNoaWZ0KGN1cnNvci5wcm9wZXJ0eT8udmFsdWUpO1xuICAgICAgICBjdXJzb3IgPSBjdXJzb3Iub2JqZWN0O1xuICAgICAgfVxuICAgICAgaWYgKGN1cnNvcj8udHlwZSA9PT0gXCJJZGVudGlmaWVyXCIpIHtcbiAgICAgICAgcGFydHMudW5zaGlmdChjdXJzb3IudmFsdWUpO1xuICAgICAgICByb290TmFtZSA9IGN1cnNvci52YWx1ZTtcbiAgICAgIH1cbiAgICAgIGVsZW1lbnROYW1lID0gcGFydHMuam9pbihcIi5cIik7XG4gICAgfVxuXG4gICAgaWYgKCFyb290TmFtZSB8fCAhdWlDb21wb25lbnRzLmhhcyhyb290TmFtZSkpIHJldHVybjtcblxuICAgIGNvbnN0IHVzYWdlOiBQcm9wVXNhZ2UgPSB7XG4gICAgICBjb21wb25lbnQ6IGVsZW1lbnROYW1lISxcbiAgICAgIHByb3BzOiBuZXcgTWFwKCksXG4gICAgICBib29sZWFuUHJvcHM6IG5ldyBTZXQoKSxcbiAgICAgIGhhc1NwcmVhZDogZmFsc2UsXG4gICAgfTtcblxuICAgIGZvciAoY29uc3QgYXR0ciBvZiBub2RlLmF0dHJpYnV0ZXMgfHwgW10pIHtcbiAgICAgIGlmIChhdHRyLnR5cGUgPT09IFwiU3ByZWFkRWxlbWVudFwiIHx8IGF0dHIudHlwZSA9PT0gXCJKU1hTcHJlYWRBdHRyaWJ1dGVcIikge1xuICAgICAgICB1c2FnZS5oYXNTcHJlYWQgPSB0cnVlO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChhdHRyLnR5cGUgIT09IFwiSlNYQXR0cmlidXRlXCIpIGNvbnRpbnVlO1xuXG4gICAgICBjb25zdCBwcm9wTmFtZSA9IGF0dHIubmFtZT8udmFsdWU7XG4gICAgICBpZiAoIXByb3BOYW1lKSBjb250aW51ZTtcblxuICAgICAgaWYgKCFhdHRyLnZhbHVlKSB7XG4gICAgICAgIHVzYWdlLmJvb2xlYW5Qcm9wcy5hZGQocHJvcE5hbWUpO1xuICAgICAgfSBlbHNlIGlmIChhdHRyLnZhbHVlLnR5cGUgPT09IFwiU3RyaW5nTGl0ZXJhbFwiKSB7XG4gICAgICAgIHVzYWdlLnByb3BzLnNldChwcm9wTmFtZSwgYXR0ci52YWx1ZS52YWx1ZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB1c2FnZS5wcm9wcy5zZXQocHJvcE5hbWUsIFwiRFlOQU1JQ1wiKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICB1c2FnZXMucHVzaCh1c2FnZSk7XG4gIH0pO1xuXG4gIHJldHVybiB1c2FnZXM7XG59XG5cbi8vIOKUgOKUgCBTYWZlbGlzdCBidWlsZGVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5pbnRlcmZhY2UgU2FmZWxpc3RzIHtcbiAgY2xhc3NTYWZlbGlzdDogU2V0PHN0cmluZz47XG4gIGF0dHJTYWZlbGlzdDogU2V0PHN0cmluZz47XG59XG5cbmZ1bmN0aW9uIGJ1aWxkU2FmZWxpc3RzKFxuICBhbGxVc2FnZXM6IFByb3BVc2FnZVtdLFxuICBtYW5pZmVzdDogUHVyZ2VNYW5pZmVzdCxcbik6IFNhZmVsaXN0cyB7XG4gIGNvbnN0IGNsYXNzU2FmZWxpc3QgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgY29uc3QgYXR0clNhZmVsaXN0ID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cbiAgY29uc3QgY29tcG9uZW50VXNhZ2VzID0gbmV3IE1hcDxzdHJpbmcsIFByb3BVc2FnZVtdPigpO1xuICBmb3IgKGNvbnN0IHVzYWdlIG9mIGFsbFVzYWdlcykge1xuICAgIGNvbnN0IGV4aXN0aW5nID0gY29tcG9uZW50VXNhZ2VzLmdldCh1c2FnZS5jb21wb25lbnQpID8/IFtdO1xuICAgIGV4aXN0aW5nLnB1c2godXNhZ2UpO1xuICAgIGNvbXBvbmVudFVzYWdlcy5zZXQodXNhZ2UuY29tcG9uZW50LCBleGlzdGluZyk7XG4gIH1cblxuICBmb3IgKGNvbnN0IFtlbnRyeU5hbWUsIGVudHJ5XSBvZiBPYmplY3QuZW50cmllcyhtYW5pZmVzdCkpIHtcbiAgICBjb25zdCBtYXRjaGluZ1VzYWdlcyA9IGZpbmRNYXRjaGluZ1VzYWdlcyhlbnRyeU5hbWUsIGNvbXBvbmVudFVzYWdlcyk7XG5cbiAgICBpZiAobWF0Y2hpbmdVc2FnZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IGNscyBvZiBlbnRyeS5jbGFzc2VzLmFsd2F5cykge1xuICAgICAgY2xhc3NTYWZlbGlzdC5hZGQoY2xzKTtcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IFtwcm9wT3JTbG90LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMoZW50cnkuY2xhc3Nlcy5ieVByb3ApKSB7XG4gICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHtcbiAgICAgICAgaWYgKGlzUHJvcFVzZWQocHJvcE9yU2xvdCwgbWF0Y2hpbmdVc2FnZXMpKSB7XG4gICAgICAgICAgZm9yIChjb25zdCBjbHMgb2YgdmFsdWUpIGNsYXNzU2FmZWxpc3QuYWRkKGNscyk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IHVzZWRWYWx1ZXMgPSBnZXRVc2VkRW51bVZhbHVlcyhwcm9wT3JTbG90LCBtYXRjaGluZ1VzYWdlcyk7XG4gICAgICAgIGlmICh1c2VkVmFsdWVzID09PSBcIkFMTFwiKSB7XG4gICAgICAgICAgZm9yIChjb25zdCBjbGFzc2VzIG9mIE9iamVjdC52YWx1ZXModmFsdWUpKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGNscyBvZiBjbGFzc2VzKSBjbGFzc1NhZmVsaXN0LmFkZChjbHMpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmb3IgKGNvbnN0IHZhbCBvZiB1c2VkVmFsdWVzKSB7XG4gICAgICAgICAgICBpZiAodmFsdWVbdmFsXSkge1xuICAgICAgICAgICAgICBmb3IgKGNvbnN0IGNscyBvZiB2YWx1ZVt2YWxdKSBjbGFzc1NhZmVsaXN0LmFkZChjbHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChlbnRyeS5hdHRycykge1xuICAgICAgZm9yIChjb25zdCBbcHJvcE5hbWUsIGF0dHJNYXBdIG9mIE9iamVjdC5lbnRyaWVzKGVudHJ5LmF0dHJzKSkge1xuICAgICAgICBpZiAoaXNQcm9wVXNlZChwcm9wTmFtZSwgbWF0Y2hpbmdVc2FnZXMpKSB7XG4gICAgICAgICAgZm9yIChjb25zdCBbYXR0ciwgdmFsXSBvZiBPYmplY3QuZW50cmllcyhhdHRyTWFwKSkge1xuICAgICAgICAgICAgYXR0clNhZmVsaXN0LmFkZChgJHthdHRyfT0ke3ZhbH1gKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4geyBjbGFzc1NhZmVsaXN0LCBhdHRyU2FmZWxpc3QgfTtcbn1cblxuZnVuY3Rpb24gZmluZE1hdGNoaW5nVXNhZ2VzKFxuICBlbnRyeU5hbWU6IHN0cmluZyxcbiAgdXNhZ2VNYXA6IE1hcDxzdHJpbmcsIFByb3BVc2FnZVtdPixcbik6IFByb3BVc2FnZVtdIHtcbiAgaWYgKHVzYWdlTWFwLmhhcyhlbnRyeU5hbWUpKSB7XG4gICAgcmV0dXJuIHVzYWdlTWFwLmdldChlbnRyeU5hbWUpITtcbiAgfVxuXG4gIGNvbnN0IHJlc3VsdHM6IFByb3BVc2FnZVtdID0gW107XG4gIGZvciAoY29uc3QgW3VzYWdlTmFtZSwgdXNhZ2VzXSBvZiB1c2FnZU1hcCkge1xuICAgIGlmICh1c2FnZU5hbWUgPT09IGVudHJ5TmFtZSkge1xuICAgICAgcmVzdWx0cy5wdXNoKC4uLnVzYWdlcyk7XG4gICAgfVxuICAgIGlmIChlbnRyeU5hbWUuaW5jbHVkZXMoXCIuXCIpKSB7XG4gICAgICBjb25zdCBbZmFtaWx5LCBwYXJ0XSA9IGVudHJ5TmFtZS5zcGxpdChcIi5cIik7XG4gICAgICBpZiAocGFydCA9PT0gZmFtaWx5ICYmIHVzYWdlTmFtZSA9PT0gZmFtaWx5KSB7XG4gICAgICAgIHJlc3VsdHMucHVzaCguLi51c2FnZXMpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0cztcbn1cblxuZnVuY3Rpb24gaXNQcm9wVXNlZChwcm9wTmFtZTogc3RyaW5nLCB1c2FnZXM6IFByb3BVc2FnZVtdKTogYm9vbGVhbiB7XG4gIGZvciAoY29uc3QgdXNhZ2Ugb2YgdXNhZ2VzKSB7XG4gICAgaWYgKHVzYWdlLmhhc1NwcmVhZCkgcmV0dXJuIHRydWU7XG4gICAgaWYgKHVzYWdlLmJvb2xlYW5Qcm9wcy5oYXMocHJvcE5hbWUpKSByZXR1cm4gdHJ1ZTtcbiAgICBpZiAodXNhZ2UucHJvcHMuaGFzKHByb3BOYW1lKSkgcmV0dXJuIHRydWU7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG5mdW5jdGlvbiBnZXRVc2VkRW51bVZhbHVlcyhzbG90TmFtZTogc3RyaW5nLCB1c2FnZXM6IFByb3BVc2FnZVtdKTogU2V0PHN0cmluZz4gfCBcIkFMTFwiIHtcbiAgY29uc3QgdmFsdWVzID0gbmV3IFNldDxzdHJpbmc+KCk7XG4gIGZvciAoY29uc3QgdXNhZ2Ugb2YgdXNhZ2VzKSB7XG4gICAgaWYgKHVzYWdlLmhhc1NwcmVhZCkgcmV0dXJuIFwiQUxMXCI7XG4gICAgY29uc3QgdmFsID0gdXNhZ2UucHJvcHMuZ2V0KHNsb3ROYW1lKTtcbiAgICBpZiAodmFsID09PSBcIkRZTkFNSUNcIikgcmV0dXJuIFwiQUxMXCI7XG4gICAgaWYgKHZhbCAhPT0gdW5kZWZpbmVkKSB2YWx1ZXMuYWRkKHZhbCk7XG4gICAgaWYgKHVzYWdlLmJvb2xlYW5Qcm9wcy5oYXMoc2xvdE5hbWUpKSByZXR1cm4gXCJBTExcIjtcbiAgfVxuICByZXR1cm4gdmFsdWVzO1xufVxuXG4vLyDilIDilIAgQ29uc3VtZXIgc291cmNlIHNjYW5uaW5nIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5hc3luYyBmdW5jdGlvbiBzY2FuQ29uc3VtZXJTb3VyY2Uoc3JjRGlyOiBzdHJpbmcpOiBQcm9taXNlPFByb3BVc2FnZVtdPiB7XG4gIGNvbnN0IGFsbFVzYWdlczogUHJvcFVzYWdlW10gPSBbXTtcbiAgY29uc3QgZ2xvYiA9IG5ldyBHbG9iKFwiKiovKi57dHN4LHRzLGpzeCxqc31cIik7XG5cbiAgZm9yIGF3YWl0IChjb25zdCByZWxQYXRoIG9mIGdsb2Iuc2Nhbih7IGN3ZDogc3JjRGlyIH0pKSB7XG4gICAgaWYgKHJlbFBhdGguaW5jbHVkZXMoXCJub2RlX21vZHVsZXNcIikpIGNvbnRpbnVlO1xuICAgIGNvbnN0IGZ1bGxQYXRoID0gcGF0aC5qb2luKHNyY0RpciwgcmVsUGF0aCk7XG4gICAgY29uc3QgY29kZSA9IGF3YWl0IEJ1bi5maWxlKGZ1bGxQYXRoKS50ZXh0KCk7XG4gICAgaWYgKCFjb2RlLmluY2x1ZGVzKFwiQHBhdGhzY2FsZS91aVwiKSkgY29udGludWU7XG5cbiAgICBjb25zdCBpc1RzeCA9IC9cXC5bdGpdc3gkLy50ZXN0KHJlbFBhdGgpO1xuICAgIGNvbnN0IGFzdCA9IGF3YWl0IHN3Yy5wYXJzZShjb2RlLCB7IHN5bnRheDogXCJ0eXBlc2NyaXB0XCIsIHRzeDogaXNUc3ggfSk7XG4gICAgY29uc3QgdWlJbXBvcnRzID0gZXh0cmFjdFVJSW1wb3J0cyhhc3QpO1xuICAgIGlmICh1aUltcG9ydHMuc2l6ZSA9PT0gMCkgY29udGludWU7XG5cbiAgICBhbGxVc2FnZXMucHVzaCguLi5leHRyYWN0SlNYVXNhZ2VzKGFzdCwgdWlJbXBvcnRzKSk7XG4gIH1cblxuICByZXR1cm4gYWxsVXNhZ2VzO1xufVxuXG4vLyDilIDilIAgTWFpbiAoc3RhbmRhbG9uZSBDTEkpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5hc3luYyBmdW5jdGlvbiBtYWluKCkge1xuICBjb25zdCBbc3JjRGlyLCBtYW5pZmVzdFBhdGhdID0gcHJvY2Vzcy5hcmd2LnNsaWNlKDIpO1xuICBpZiAoIXNyY0RpciB8fCAhbWFuaWZlc3RQYXRoKSB7XG4gICAgY29uc29sZS5lcnJvcihcIlVzYWdlOiBidW4gcnVuIHNyYy9zY2FuLWNvbnN1bWVyLnRzIDxjb25zdW1lci1zcmMtZGlyPiA8cHVyZ2UtbWFuaWZlc3QuanNvbj5cIik7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG5cbiAgY29uc3QgbWFuaWZlc3Q6IFB1cmdlTWFuaWZlc3QgPSBKU09OLnBhcnNlKFxuICAgIGF3YWl0IEJ1bi5maWxlKG1hbmlmZXN0UGF0aCkudGV4dCgpLFxuICApO1xuICBjb25zdCByZXNvbHZlZFNyYyA9IHBhdGgucmVzb2x2ZShzcmNEaXIpO1xuICBjb25zb2xlLmxvZyhgU2Nhbm5pbmcgJHtyZXNvbHZlZFNyY30gZm9yIEBwYXRoc2NhbGUvdWkgY29tcG9uZW50IHVzYWdl4oCmYCk7XG4gIGNvbnNvbGUubG9nKGBNYW5pZmVzdDogJHtPYmplY3Qua2V5cyhtYW5pZmVzdCkubGVuZ3RofSBlbnRyaWVzXFxuYCk7XG5cbiAgY29uc3QgdXNhZ2VzID0gYXdhaXQgc2NhbkNvbnN1bWVyU291cmNlKHJlc29sdmVkU3JjKTtcbiAgY29uc3QgeyBjbGFzc1NhZmVsaXN0LCBhdHRyU2FmZWxpc3QgfSA9IGJ1aWxkU2FmZWxpc3RzKHVzYWdlcywgbWFuaWZlc3QpO1xuXG4gIGNvbnNvbGUubG9nKFwiPT09IENsYXNzIFNhZmVsaXN0ID09PVwiKTtcbiAgZm9yIChjb25zdCBjbHMgb2YgWy4uLmNsYXNzU2FmZWxpc3RdLnNvcnQoKSkge1xuICAgIGNvbnNvbGUubG9nKGAgICR7Y2xzfWApO1xuICB9XG5cbiAgY29uc29sZS5sb2coYFxcbj09PSBBdHRyaWJ1dGUgU2FmZWxpc3QgPT09YCk7XG4gIGZvciAoY29uc3QgYXR0ciBvZiBbLi4uYXR0clNhZmVsaXN0XS5zb3J0KCkpIHtcbiAgICBjb25zb2xlLmxvZyhgICBbJHthdHRyfV1gKTtcbiAgfVxuXG4gIGNvbnNvbGUubG9nKGBcXG5Ub3RhbDogJHtjbGFzc1NhZmVsaXN0LnNpemV9IGNsYXNzZXMsICR7YXR0clNhZmVsaXN0LnNpemV9IGF0dHJpYnV0ZSBzZWxlY3RvcnNgKTtcbn1cblxuLy8gT25seSBydW4gQ0xJIHdoZW4gaW52b2tlZCBkaXJlY3RseSAobm90IHdoZW4gaW1wb3J0ZWQgYXMgYSBtb2R1bGUpXG5pZiAoaW1wb3J0Lm1ldGEubWFpbikge1xuICBtYWluKCk7XG59XG5cbmV4cG9ydCB7IGV4dHJhY3RVSUltcG9ydHMsIGV4dHJhY3RKU1hVc2FnZXMsIGJ1aWxkU2FmZWxpc3RzLCBzY2FuQ29uc3VtZXJTb3VyY2UgfTtcbmV4cG9ydCB0eXBlIHsgUHJvcFVzYWdlLCBQdXJnZU1hbmlmZXN0LCBDb21wb25lbnRNYW5pZmVzdCwgU2FmZWxpc3RzIH07XG4iCiAgXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQVVBO0FBQ0E7QUFDQTtBQXlCQSxTQUFTLE9BQU8sQ0FBQyxNQUFXLFNBQThCO0FBQUEsRUFDeEQsSUFBSSxDQUFDLFFBQVEsT0FBTyxTQUFTO0FBQUEsSUFBVTtBQUFBLEVBQ3ZDLFFBQVEsSUFBSTtBQUFBLEVBQ1osV0FBVyxPQUFPLE9BQU8sS0FBSyxJQUFJLEdBQUc7QUFBQSxJQUNuQyxJQUFJLFFBQVE7QUFBQSxNQUFRO0FBQUEsSUFDcEIsTUFBTSxNQUFNLEtBQUs7QUFBQSxJQUNqQixJQUFJLE1BQU0sUUFBUSxHQUFHLEdBQUc7QUFBQSxNQUN0QixXQUFXLFFBQVE7QUFBQSxRQUFLLFFBQVEsTUFBTSxPQUFPO0FBQUEsSUFDL0MsRUFBTyxTQUFJLE9BQU8sT0FBTyxRQUFRLFVBQVU7QUFBQSxNQUN6QyxR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QVMsUUFBUTtBQUFBLElBQzFCLElBQUksTUFBTTtBQUFBLE1BQVcsT0FBTztBQUFBLElBQzVCLElBQUksTUFBTSxhQUFhLElBQUksUUFBUTtBQUFBLE1BQUcsT0FBTztBQUFBLElBQzdDLElBQUksTUFBTSxNQUFNLElBQUksUUFBUTtBQUFBLE1BQUcsT0FBTztBQUFBLEVBQ3hDO0FBQUEsRUFDQSxPQUFPO0FBQUE7QUFHVCxTQUFTLGlCQUFpQixDQUFDLFVBQWtCLFFBQTBDO0FBQUEsRUFDckYsTUFBTSxTQUFTLElBQUk7QUFBQSxFQUNuQixXQUFXLFNBQVMsUUFBUTtBQUFBLElBQzFCLElBQUksTUFBTTtBQUFBLE1BQVcsT0FBTztBQUFBLElBQzVCLE1BQU0sTUFBTSxNQUFNLE1BQU0sSUFBSSxRQUFRO0FBQUEsSUFDcEMsSUFBSSxRQUFRO0FBQUEsTUFBVyxPQUFPO0FBQUEsSUFDOUIsSUFBSSxRQUFRO0FBQUEsTUFBVyxPQUFPLElBQUksR0FBRztBQUFBLElBQ3JDLElBQUksTUFBTSxhQUFhLElBQUksUUFBUTtBQUFBLE1BQUcsT0FBTztBQUFBLEVBQy9DO0FBQUEsRUFDQSxPQUFPO0FBQUE7QUFLVCxlQUFlLGtCQUFrQixDQUFDLFFBQXNDO0FBQUEsRUFDdEUsTUFBTSxZQUF5QixDQUFDO0FBQUEsRUFDaEMsTUFBTSxPQUFPLElBQUksS0FBSyxzQkFBc0I7QUFBQSxFQUU1QyxpQkFBaUIsV0FBVyxLQUFLLEtBQUssRUFBRSxLQUFLLE9BQU8sQ0FBQyxHQUFHO0FBQUEsSUFDdEQsSUFBSSxRQUFRLFNBQVMsY0FBYztBQUFBLE1BQUc7QUFBQSxJQUN0QyxNQUFNLFdBQVcsS0FBSyxLQUFLLFFBQVEsT0FBTztBQUFBLElBQzFDLE1BQU0sT0FBTyxNQUFNLElBQUksS0FBSyxRQUFRLEVBQUUsS0FBSztBQUFBLElBQzNDLElBQUksQ0FBQyxLQUFLLFNBQVMsZUFBZTtBQUFBLE1BQUc7QUFBQSxJQUVyQyxNQUFNLFFBQVEsWUFBWSxLQUFLLE9BQU87QUFBQSxJQUN0QyxNQUFNLE1BQU0sTUFBTSxJQUFJLE1BQU0sTUFBTSxFQUFFLFFBQVEsY0FBYyxLQUFLLE1BQU0sQ0FBQztBQUFBLElBQ3RFLE1BQU0sWUFBWSxpQkFBaUIsR0FBRztBQUFBLElBQ3RDLElBQUksVUFBVSxTQUFTO0FBQUEsTUFBRztBQUFBLElBRTFCLFVBQVUsS0FBSyxHQUFHLGlCQUFpQixLQUFLLFNBQVMsQ0FBQztBQUFBLEVBQ3BEO0FBQUEsRUFFQSxPQUFPO0FBQUE7QUFvQ1QsSUFBSSxPQUFrQixDQUV0QjsiLAogICJkZWJ1Z0lkIjogIjU1MDk1NEFBNDQzQUQ4RjA2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Postbuild CSS purge — standalone Bun script.
4
+ *
5
+ * Runs after rsbuild build, purges CSS files in dist/ using the purge manifest
6
+ * and consumer JSX analysis. Zero Node imports.
7
+ *
8
+ * Three-level purge:
9
+ * L1 — class-level: drop rules whose class selectors aren't in the safelist (postcss)
10
+ * L2 — attr-level: drop rules with data/aria attribute selectors for unused props (postcss)
11
+ * L3 — var cleanup: iteratively remove declared-but-unreferenced CSS custom properties (postcss)
12
+ * Final — minification via Lightning CSS
13
+ *
14
+ * Usage:
15
+ * bunx @pathscale/rsbuild-plugin-ui-css-purge \
16
+ * --dist dist --src src --manifest node_modules/@pathscale/ui/dist/purge-manifest.json
17
+ */
18
+ export {};