@pathscale/rebuild-plugin-ui-css-purge 0.1.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 +1 -0
- package/dist/generate-manifest.d.ts +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +337 -0
- package/dist/rsbuild-plugin.d.ts +28 -0
- package/dist/scan-consumer.d.ts +35 -0
- package/package.json +35 -0
- package/src/generate-manifest.ts +155 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# bun-plugin-ui-css-purge
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lib-side database generator.
|
|
3
|
+
*
|
|
4
|
+
* Reads all `*.classnames.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 {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// src/rsbuild-plugin.ts
|
|
2
|
+
import { PurgeCSS } from "purgecss";
|
|
3
|
+
import postcss from "postcss";
|
|
4
|
+
import swc from "@swc/core";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { readFile as nodeReadFile } from "node:fs/promises";
|
|
7
|
+
import { glob as fastGlob } from "fast-glob";
|
|
8
|
+
function walkAST(node, visitor) {
|
|
9
|
+
if (!node || typeof node !== "object")
|
|
10
|
+
return;
|
|
11
|
+
visitor(node);
|
|
12
|
+
for (const key of Object.keys(node)) {
|
|
13
|
+
if (key === "span")
|
|
14
|
+
continue;
|
|
15
|
+
const val = node[key];
|
|
16
|
+
if (Array.isArray(val)) {
|
|
17
|
+
for (const item of val)
|
|
18
|
+
walkAST(item, visitor);
|
|
19
|
+
} else if (val && typeof val === "object") {
|
|
20
|
+
walkAST(val, visitor);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function extractUIImports(ast) {
|
|
25
|
+
const imports = new Map;
|
|
26
|
+
for (const node of ast.body) {
|
|
27
|
+
if (node.type !== "ImportDeclaration")
|
|
28
|
+
continue;
|
|
29
|
+
const src = node.source?.value;
|
|
30
|
+
if (!src || !src.startsWith("@pathscale/ui"))
|
|
31
|
+
continue;
|
|
32
|
+
for (const spec of node.specifiers) {
|
|
33
|
+
if (spec.type === "ImportSpecifier") {
|
|
34
|
+
const imported = spec.imported?.value ?? spec.local?.value;
|
|
35
|
+
const local = spec.local?.value;
|
|
36
|
+
if (local && imported)
|
|
37
|
+
imports.set(local, imported);
|
|
38
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
39
|
+
const local = spec.local?.value;
|
|
40
|
+
if (local)
|
|
41
|
+
imports.set(local, local);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return imports;
|
|
46
|
+
}
|
|
47
|
+
function extractJSXUsages(ast, uiComponents) {
|
|
48
|
+
const usages = [];
|
|
49
|
+
walkAST(ast, (node) => {
|
|
50
|
+
if (node.type !== "JSXOpeningElement")
|
|
51
|
+
return;
|
|
52
|
+
let elementName = null;
|
|
53
|
+
let rootName = null;
|
|
54
|
+
if (node.name?.type === "Identifier") {
|
|
55
|
+
elementName = node.name.value;
|
|
56
|
+
rootName = elementName;
|
|
57
|
+
} else if (node.name?.type === "JSXMemberExpression") {
|
|
58
|
+
const parts = [];
|
|
59
|
+
let cursor = node.name;
|
|
60
|
+
while (cursor?.type === "JSXMemberExpression") {
|
|
61
|
+
parts.unshift(cursor.property?.value);
|
|
62
|
+
cursor = cursor.object;
|
|
63
|
+
}
|
|
64
|
+
if (cursor?.type === "Identifier") {
|
|
65
|
+
parts.unshift(cursor.value);
|
|
66
|
+
rootName = cursor.value;
|
|
67
|
+
}
|
|
68
|
+
elementName = parts.join(".");
|
|
69
|
+
}
|
|
70
|
+
if (!rootName || !uiComponents.has(rootName))
|
|
71
|
+
return;
|
|
72
|
+
const usage = {
|
|
73
|
+
component: elementName,
|
|
74
|
+
props: new Map,
|
|
75
|
+
booleanProps: new Set,
|
|
76
|
+
hasSpread: false
|
|
77
|
+
};
|
|
78
|
+
for (const attr of node.attributes || []) {
|
|
79
|
+
if (attr.type === "SpreadElement" || attr.type === "JSXSpreadAttribute") {
|
|
80
|
+
usage.hasSpread = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (attr.type !== "JSXAttribute")
|
|
84
|
+
continue;
|
|
85
|
+
const propName = attr.name?.value;
|
|
86
|
+
if (!propName)
|
|
87
|
+
continue;
|
|
88
|
+
if (!attr.value) {
|
|
89
|
+
usage.booleanProps.add(propName);
|
|
90
|
+
} else if (attr.value.type === "StringLiteral") {
|
|
91
|
+
usage.props.set(propName, attr.value.value);
|
|
92
|
+
} else {
|
|
93
|
+
usage.props.set(propName, "DYNAMIC");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
usages.push(usage);
|
|
97
|
+
});
|
|
98
|
+
return usages;
|
|
99
|
+
}
|
|
100
|
+
function buildSafelists(allUsages, manifest) {
|
|
101
|
+
const classSafelist = new Set;
|
|
102
|
+
const attrSafelist = new Set;
|
|
103
|
+
const componentUsages = new Map;
|
|
104
|
+
for (const usage of allUsages) {
|
|
105
|
+
const existing = componentUsages.get(usage.component) ?? [];
|
|
106
|
+
existing.push(usage);
|
|
107
|
+
componentUsages.set(usage.component, existing);
|
|
108
|
+
}
|
|
109
|
+
for (const [entryName, entry] of Object.entries(manifest)) {
|
|
110
|
+
const matchingUsages = findMatchingUsages(entryName, componentUsages);
|
|
111
|
+
if (matchingUsages.length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
for (const cls of entry.classes.always)
|
|
114
|
+
classSafelist.add(cls);
|
|
115
|
+
for (const [propOrSlot, value] of Object.entries(entry.classes.byProp)) {
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
if (isPropUsed(propOrSlot, matchingUsages)) {
|
|
118
|
+
for (const cls of value)
|
|
119
|
+
classSafelist.add(cls);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
const usedValues = getUsedEnumValues(propOrSlot, matchingUsages);
|
|
123
|
+
if (usedValues === "ALL") {
|
|
124
|
+
for (const classes of Object.values(value)) {
|
|
125
|
+
for (const cls of classes)
|
|
126
|
+
classSafelist.add(cls);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
for (const val of usedValues) {
|
|
130
|
+
if (value[val]) {
|
|
131
|
+
for (const cls of value[val])
|
|
132
|
+
classSafelist.add(cls);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (entry.attrs) {
|
|
139
|
+
for (const [propName, attrMap] of Object.entries(entry.attrs)) {
|
|
140
|
+
if (isPropUsed(propName, matchingUsages)) {
|
|
141
|
+
for (const [attr, val] of Object.entries(attrMap)) {
|
|
142
|
+
attrSafelist.add(`[${attr}="${val}"]`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { classSafelist, attrSafelist };
|
|
149
|
+
}
|
|
150
|
+
function findMatchingUsages(entryName, usageMap) {
|
|
151
|
+
if (usageMap.has(entryName))
|
|
152
|
+
return usageMap.get(entryName);
|
|
153
|
+
const results = [];
|
|
154
|
+
for (const [usageName, usages] of usageMap) {
|
|
155
|
+
if (usageName === entryName) {
|
|
156
|
+
results.push(...usages);
|
|
157
|
+
}
|
|
158
|
+
if (entryName.includes(".")) {
|
|
159
|
+
const [family, part] = entryName.split(".");
|
|
160
|
+
if (part === family && usageName === family) {
|
|
161
|
+
results.push(...usages);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
function isPropUsed(propName, usages) {
|
|
168
|
+
for (const usage of usages) {
|
|
169
|
+
if (usage.hasSpread)
|
|
170
|
+
return true;
|
|
171
|
+
if (usage.booleanProps.has(propName))
|
|
172
|
+
return true;
|
|
173
|
+
if (usage.props.has(propName))
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
function getUsedEnumValues(slotName, usages) {
|
|
179
|
+
const values = new Set;
|
|
180
|
+
for (const usage of usages) {
|
|
181
|
+
if (usage.hasSpread)
|
|
182
|
+
return "ALL";
|
|
183
|
+
const val = usage.props.get(slotName);
|
|
184
|
+
if (val === "DYNAMIC")
|
|
185
|
+
return "ALL";
|
|
186
|
+
if (val !== undefined)
|
|
187
|
+
values.add(val);
|
|
188
|
+
if (usage.booleanProps.has(slotName))
|
|
189
|
+
return "ALL";
|
|
190
|
+
}
|
|
191
|
+
return values;
|
|
192
|
+
}
|
|
193
|
+
async function scanConsumerSource(srcDir) {
|
|
194
|
+
const allUsages = [];
|
|
195
|
+
const files = await fastGlob("**/*.{tsx,ts,jsx,js}", { cwd: srcDir, ignore: ["**/node_modules/**"] });
|
|
196
|
+
for (const relPath of files) {
|
|
197
|
+
const fullPath = path.join(srcDir, relPath);
|
|
198
|
+
const code = await nodeReadFile(fullPath, "utf-8");
|
|
199
|
+
if (!code.includes("@pathscale/ui"))
|
|
200
|
+
continue;
|
|
201
|
+
const isTsx = /\.[tj]sx$/.test(relPath);
|
|
202
|
+
const ast = await swc.parse(code, { syntax: "typescript", tsx: isTsx });
|
|
203
|
+
const uiImports = extractUIImports(ast);
|
|
204
|
+
if (uiImports.size === 0)
|
|
205
|
+
continue;
|
|
206
|
+
allUsages.push(...extractJSXUsages(ast, uiImports));
|
|
207
|
+
}
|
|
208
|
+
return allUsages;
|
|
209
|
+
}
|
|
210
|
+
function purgeAttributes(css, attrSafelist) {
|
|
211
|
+
const root = postcss.parse(css);
|
|
212
|
+
root.walkRules((rule) => {
|
|
213
|
+
const selectors = rule.selectors;
|
|
214
|
+
const kept = [];
|
|
215
|
+
for (const sel of selectors) {
|
|
216
|
+
const attrMatches = sel.matchAll(/\[(data-[a-z-]+|aria-[a-z-]+)="([^"]+)"\]/g);
|
|
217
|
+
let shouldKeep = true;
|
|
218
|
+
for (const match of attrMatches) {
|
|
219
|
+
const attrSelector = `[${match[1]}="${match[2]}"]`;
|
|
220
|
+
if (match[1] === "data-slot")
|
|
221
|
+
continue;
|
|
222
|
+
if (!attrSafelist.has(attrSelector)) {
|
|
223
|
+
shouldKeep = false;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (shouldKeep)
|
|
228
|
+
kept.push(sel);
|
|
229
|
+
}
|
|
230
|
+
if (kept.length === 0) {
|
|
231
|
+
rule.remove();
|
|
232
|
+
} else if (kept.length < selectors.length) {
|
|
233
|
+
rule.selectors = kept;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return root.toString();
|
|
237
|
+
}
|
|
238
|
+
function cleanUnusedVars(css) {
|
|
239
|
+
let changed = true;
|
|
240
|
+
let result = css;
|
|
241
|
+
while (changed) {
|
|
242
|
+
changed = false;
|
|
243
|
+
const root = postcss.parse(result);
|
|
244
|
+
const declared = new Map;
|
|
245
|
+
root.walkDecls(/^--/, (decl) => {
|
|
246
|
+
const entries = declared.get(decl.prop) ?? [];
|
|
247
|
+
entries.push({ rule: decl.parent, prop: decl.prop, index: entries.length });
|
|
248
|
+
declared.set(decl.prop, entries);
|
|
249
|
+
});
|
|
250
|
+
const referenced = new Set;
|
|
251
|
+
root.walkDecls((decl) => {
|
|
252
|
+
const refs = decl.value.matchAll(/var\(\s*(--[a-zA-Z0-9_-]+)/g);
|
|
253
|
+
for (const ref of refs) {
|
|
254
|
+
referenced.add(ref[1]);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
for (const [varName, entries] of declared) {
|
|
258
|
+
if (!referenced.has(varName)) {
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
entry.rule.walkDecls(entry.prop, (decl) => {
|
|
261
|
+
decl.remove();
|
|
262
|
+
changed = true;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
root.walkRules((rule) => {
|
|
268
|
+
if (rule.nodes && rule.nodes.length === 0)
|
|
269
|
+
rule.remove();
|
|
270
|
+
});
|
|
271
|
+
root.walkAtRules((atRule) => {
|
|
272
|
+
if (atRule.nodes && atRule.nodes.length === 0)
|
|
273
|
+
atRule.remove();
|
|
274
|
+
});
|
|
275
|
+
result = root.toString();
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
var pluginCssPurge = (options) => ({
|
|
280
|
+
name: "plugin-css-purge",
|
|
281
|
+
setup(api) {
|
|
282
|
+
const {
|
|
283
|
+
manifest: manifestPath,
|
|
284
|
+
srcDir = "src",
|
|
285
|
+
attrPurge = true,
|
|
286
|
+
cleanVars = true,
|
|
287
|
+
verbose = true
|
|
288
|
+
} = options;
|
|
289
|
+
api.processAssets({ stage: "optimize-size" }, async ({ assets, sources, compilation }) => {
|
|
290
|
+
const log = verbose ? console.log.bind(console) : () => {};
|
|
291
|
+
const manifest = JSON.parse(await nodeReadFile(path.resolve(manifestPath), "utf-8"));
|
|
292
|
+
log(`[css-purge] Manifest loaded: ${Object.keys(manifest).length} entries`);
|
|
293
|
+
const resolvedSrc = path.resolve(srcDir);
|
|
294
|
+
const usages = await scanConsumerSource(resolvedSrc);
|
|
295
|
+
log(`[css-purge] Scanned ${resolvedSrc}: ${usages.length} component usages`);
|
|
296
|
+
const { classSafelist, attrSafelist } = buildSafelists(usages, manifest);
|
|
297
|
+
log(`[css-purge] Safelist: ${classSafelist.size} classes, ${attrSafelist.size} attrs`);
|
|
298
|
+
for (const [name, asset] of Object.entries(assets)) {
|
|
299
|
+
if (!name.endsWith(".css"))
|
|
300
|
+
continue;
|
|
301
|
+
const originalCss = asset.source().toString();
|
|
302
|
+
const originalSize = Buffer.byteLength(originalCss, "utf-8");
|
|
303
|
+
log(`[css-purge] Processing ${name} (${(originalSize / 1024).toFixed(1)} KB)`);
|
|
304
|
+
const purgeResult = await new PurgeCSS().purge({
|
|
305
|
+
content: [],
|
|
306
|
+
css: [{ raw: originalCss }],
|
|
307
|
+
safelist: [...classSafelist],
|
|
308
|
+
keyframes: false,
|
|
309
|
+
fontFace: false
|
|
310
|
+
});
|
|
311
|
+
let purgedCss = purgeResult[0]?.css ?? originalCss;
|
|
312
|
+
const afterL1 = Buffer.byteLength(purgedCss, "utf-8");
|
|
313
|
+
log(`[css-purge] L1 class purge: ${(originalSize / 1024).toFixed(1)} → ${(afterL1 / 1024).toFixed(1)} KB`);
|
|
314
|
+
if (attrPurge && attrSafelist.size > 0) {
|
|
315
|
+
purgedCss = purgeAttributes(purgedCss, attrSafelist);
|
|
316
|
+
const afterL2 = Buffer.byteLength(purgedCss, "utf-8");
|
|
317
|
+
log(`[css-purge] L2 attr purge: ${(afterL1 / 1024).toFixed(1)} → ${(afterL2 / 1024).toFixed(1)} KB`);
|
|
318
|
+
}
|
|
319
|
+
if (cleanVars) {
|
|
320
|
+
purgedCss = cleanUnusedVars(purgedCss);
|
|
321
|
+
const afterL3 = Buffer.byteLength(purgedCss, "utf-8");
|
|
322
|
+
log(`[css-purge] L3 var cleanup: → ${(afterL3 / 1024).toFixed(1)} KB`);
|
|
323
|
+
}
|
|
324
|
+
const finalSize = Buffer.byteLength(purgedCss, "utf-8");
|
|
325
|
+
log(`[css-purge] Final: ${(originalSize / 1024).toFixed(1)} → ${(finalSize / 1024).toFixed(1)} KB (${((1 - finalSize / originalSize) * 100).toFixed(1)}% reduction)`);
|
|
326
|
+
const source = new sources.RawSource(purgedCss);
|
|
327
|
+
compilation.updateAsset(name, source);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
export {
|
|
333
|
+
pluginCssPurge
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
//# debugId=CB5B2093063663E864756E2164756E21
|
|
337
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JzYnVpbGQtcGx1Z2luLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWwogICAgIi8qKlxuICogcnNidWlsZC1wbHVnaW4tY3NzLXB1cmdlXG4gKlxuICogVHdvLWxldmVsIENTUyBwdXJnZSBmb3IgQHBhdGhzY2FsZS91aSBjb25zdW1lcnMuXG4gKlxuICogTGV2ZWwgMTogY2xhc3MtbGV2ZWwgcHVyZ2UgdmlhIHB1cmdlY3NzIOKAlCByZW1vdmVzIGVudGlyZSBydWxlcyB3aG9zZSBzZWxlY3RvcnNcbiAqICAgICAgICAgIGRvbid0IG1hdGNoIHRoZSBzYWZlbGlzdCBidWlsdCBmcm9tIGNvbnN1bWVyIEpTWCBhbmFseXNpcy5cbiAqIExldmVsIDI6IGF0dHJpYnV0ZS1sZXZlbCBwdXJnZSDigJQgd2l0aGluIGtlcHQgcnVsZXMsIHN0cmlwcyBjb21wb3VuZCBzZWxlY3RvcnNcbiAqICAgICAgICAgIGNvbnRhaW5pbmcgZGF0YS1hdHRyIC8gYXJpYS1hdHRyIGF0dHJpYnV0ZSBzZWxlY3RvcnMgbm90IGluIHRoZSBhdHRyIHNhZmVsaXN0LlxuICpcbiAqIFVzYWdlIGluIHJzYnVpbGQuY29uZmlnLnRzOlxuICogICBpbXBvcnQgeyBwbHVnaW5Dc3NQdXJnZSB9IGZyb20gXCJAcGF0aHNjYWxlL3JlYnVpbGQtcGx1Z2luLXVpLWNzcy1wdXJnZVwiO1xuICogICBleHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoeyBwbHVnaW5zOiBbcGx1Z2luQ3NzUHVyZ2UoeyBtYW5pZmVzdDogXCIuLi5cIiB9KV0gfSk7XG4gKi9cblxuaW1wb3J0IHR5cGUgeyBSc2J1aWxkUGx1Z2luIH0gZnJvbSBcIkByc2J1aWxkL2NvcmVcIjtcbmltcG9ydCB7IFB1cmdlQ1NTIH0gZnJvbSBcInB1cmdlY3NzXCI7XG5pbXBvcnQgcG9zdGNzcyBmcm9tIFwicG9zdGNzc1wiO1xuaW1wb3J0IHR5cGUgeyBSdWxlLCBBdFJ1bGUgfSBmcm9tIFwicG9zdGNzc1wiO1xuaW1wb3J0IHN3YyBmcm9tIFwiQHN3Yy9jb3JlXCI7XG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuaW1wb3J0IHsgcmVhZEZpbGUgYXMgbm9kZVJlYWRGaWxlIH0gZnJvbSBcIm5vZGU6ZnMvcHJvbWlzZXNcIjtcbmltcG9ydCB7IGdsb2IgYXMgZmFzdEdsb2IgfSBmcm9tIFwiZmFzdC1nbG9iXCI7XG5cbi8vIOKUgOKUgCBUeXBlcyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuaW50ZXJmYWNlIENvbXBvbmVudE1hbmlmZXN0IHtcbiAgY2xhc3Nlczoge1xuICAgIGFsd2F5czogc3RyaW5nW107XG4gICAgYnlQcm9wOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmdbXSB8IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPj47XG4gIH07XG4gIGF0dHJzPzogUmVjb3JkPHN0cmluZywgUmVjb3JkPHN0cmluZywgc3RyaW5nPj47XG59XG5cbnR5cGUgUHVyZ2VNYW5pZmVzdCA9IFJlY29yZDxzdHJpbmcsIENvbXBvbmVudE1hbmlmZXN0PjtcblxuaW50ZXJmYWNlIFByb3BVc2FnZSB7XG4gIGNvbXBvbmVudDogc3RyaW5nO1xuICBwcm9wczogTWFwPHN0cmluZywgc3RyaW5nIHwgXCJEWU5BTUlDXCI+O1xuICBib29sZWFuUHJvcHM6IFNldDxzdHJpbmc+O1xuICBoYXNTcHJlYWQ6IGJvb2xlYW47XG59XG5cbi8vIOKUgOKUgCBQbHVnaW4gb3B0aW9ucyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuZXhwb3J0IGludGVyZmFjZSBDc3NQdXJnZU9wdGlvbnMge1xuICAvKiogUGF0aCB0byBwdXJnZS1tYW5pZmVzdC5qc29uIChnZW5lcmF0ZWQgYnkgZ2VuZXJhdGUtbWFuaWZlc3QudHMpICovXG4gIG1hbmlmZXN0OiBzdHJpbmc7XG4gIC8qKiBDb25zdW1lciBzb3VyY2UgZGlyZWN0b3J5IHRvIHNjYW4gZm9yIEpTWCB1c2FnZSAoZGVmYXVsdDogXCJzcmNcIikgKi9cbiAgc3JjRGlyPzogc3RyaW5nO1xuICAvKiogRW5hYmxlIExldmVsIDIgYXR0cmlidXRlIHB1cmdlIChkZWZhdWx0OiB0cnVlKSAqL1xuICBhdHRyUHVyZ2U/OiBib29sZWFuO1xuICAvKiogRW5hYmxlIENTUyB2YXJpYWJsZSBjbGVhbnVwIChkZWZhdWx0OiB0cnVlKSAqL1xuICBjbGVhblZhcnM/OiBib29sZWFuO1xuICAvKiogTG9nIHB1cmdlIHN0YXRzIChkZWZhdWx0OiB0cnVlKSAqL1xuICB2ZXJib3NlPzogYm9vbGVhbjtcbn1cblxuLy8g4pSA4pSAIEFTVCB1dGlsaXRpZXMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmZ1bmN0aW9uIHdhbGtBU1Qobm9kZTogYW55LCB2aXNpdG9yOiAobm9kZTogYW55KSA9PiB2b2lkKSB7XG4gIGlmICghbm9kZSB8fCB0eXBlb2Ygbm9kZSAhPT0gXCJvYmplY3RcIikgcmV0dXJuO1xuICB2aXNpdG9yKG5vZGUpO1xuICBmb3IgKGNvbnN0IGtleSBvZiBPYmplY3Qua2V5cyhub2RlKSkge1xuICAgIGlmIChrZXkgPT09IFwic3BhblwiKSBjb250aW51ZTtcbiAgICBjb25zdCB2YWwgPSBub2RlW2tleV07XG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsKSkge1xuICAgICAgZm9yIChjb25zdCBpdGVtIG9mIHZhbCkgd2Fsa0FTVChpdGVtLCB2aXNpdG9yKTtcbiAgICB9IGVsc2UgaWYgKHZhbCAmJiB0eXBlb2YgdmFsID09PSBcIm9iamVjdFwiKSB7XG4gICAgICB3YWxrQVNUKHZhbCwgdmlzaXRvcik7XG4gICAgfVxuICB9XG59XG5cbmZ1bmN0aW9uIGV4dHJhY3RVSUltcG9ydHMoYXN0OiBhbnkpOiBNYXA8c3RyaW5nLCBzdHJpbmc+IHtcbiAgY29uc3QgaW1wb3J0cyA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmc+KCk7XG4gIGZvciAoY29uc3Qgbm9kZSBvZiBhc3QuYm9keSkge1xuICAgIGlmIChub2RlLnR5cGUgIT09IFwiSW1wb3J0RGVjbGFyYXRpb25cIikgY29udGludWU7XG4gICAgY29uc3Qgc3JjID0gbm9kZS5zb3VyY2U/LnZhbHVlIGFzIHN0cmluZztcbiAgICBpZiAoIXNyYyB8fCAhc3JjLnN0YXJ0c1dpdGgoXCJAcGF0aHNjYWxlL3VpXCIpKSBjb250aW51ZTtcbiAgICBmb3IgKGNvbnN0IHNwZWMgb2Ygbm9kZS5zcGVjaWZpZXJzKSB7XG4gICAgICBpZiAoc3BlYy50eXBlID09PSBcIkltcG9ydFNwZWNpZmllclwiKSB7XG4gICAgICAgIGNvbnN0IGltcG9ydGVkID0gc3BlYy5pbXBvcnRlZD8udmFsdWUgPz8gc3BlYy5sb2NhbD8udmFsdWU7XG4gICAgICAgIGNvbnN0IGxvY2FsID0gc3BlYy5sb2NhbD8udmFsdWU7XG4gICAgICAgIGlmIChsb2NhbCAmJiBpbXBvcnRlZCkgaW1wb3J0cy5zZXQobG9jYWwsIGltcG9ydGVkKTtcbiAgICAgIH0gZWxzZSBpZiAoc3BlYy50eXBlID09PSBcIkltcG9ydERlZmF1bHRTcGVjaWZpZXJcIikge1xuICAgICAgICBjb25zdCBsb2NhbCA9IHNwZWMubG9jYWw/LnZhbHVlO1xuICAgICAgICBpZiAobG9jYWwpIGltcG9ydHMuc2V0KGxvY2FsLCBsb2NhbCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBpbXBvcnRzO1xufVxuXG5mdW5jdGlvbiBleHRyYWN0SlNYVXNhZ2VzKGFzdDogYW55LCB1aUNvbXBvbmVudHM6IE1hcDxzdHJpbmcsIHN0cmluZz4pOiBQcm9wVXNhZ2VbXSB7XG4gIGNvbnN0IHVzYWdlczogUHJvcFVzYWdlW10gPSBbXTtcbiAgd2Fsa0FTVChhc3QsIChub2RlKSA9PiB7XG4gICAgaWYgKG5vZGUudHlwZSAhPT0gXCJKU1hPcGVuaW5nRWxlbWVudFwiKSByZXR1cm47XG4gICAgbGV0IGVsZW1lbnROYW1lOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgICBsZXQgcm9vdE5hbWU6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuICAgIGlmIChub2RlLm5hbWU/LnR5cGUgPT09IFwiSWRlbnRpZmllclwiKSB7XG4gICAgICBlbGVtZW50TmFtZSA9IG5vZGUubmFtZS52YWx1ZTtcbiAgICAgIHJvb3ROYW1lID0gZWxlbWVudE5hbWU7XG4gICAgfSBlbHNlIGlmIChub2RlLm5hbWU/LnR5cGUgPT09IFwiSlNYTWVtYmVyRXhwcmVzc2lvblwiKSB7XG4gICAgICBjb25zdCBwYXJ0czogc3RyaW5nW10gPSBbXTtcbiAgICAgIGxldCBjdXJzb3IgPSBub2RlLm5hbWU7XG4gICAgICB3aGlsZSAoY3Vyc29yPy50eXBlID09PSBcIkpTWE1lbWJlckV4cHJlc3Npb25cIikge1xuICAgICAgICBwYXJ0cy51bnNoaWZ0KGN1cnNvci5wcm9wZXJ0eT8udmFsdWUpO1xuICAgICAgICBjdXJzb3IgPSBjdXJzb3Iub2JqZWN0O1xuICAgICAgfVxuICAgICAgaWYgKGN1cnNvcj8udHlwZSA9PT0gXCJJZGVudGlmaWVyXCIpIHtcbiAgICAgICAgcGFydHMudW5zaGlmdChjdXJzb3IudmFsdWUpO1xuICAgICAgICByb290TmFtZSA9IGN1cnNvci52YWx1ZTtcbiAgICAgIH1cbiAgICAgIGVsZW1lbnROYW1lID0gcGFydHMuam9pbihcIi5cIik7XG4gICAgfVxuICAgIGlmICghcm9vdE5hbWUgfHwgIXVpQ29tcG9uZW50cy5oYXMocm9vdE5hbWUpKSByZXR1cm47XG4gICAgY29uc3QgdXNhZ2U6IFByb3BVc2FnZSA9IHtcbiAgICAgIGNvbXBvbmVudDogZWxlbWVudE5hbWUhLFxuICAgICAgcHJvcHM6IG5ldyBNYXAoKSxcbiAgICAgIGJvb2xlYW5Qcm9wczogbmV3IFNldCgpLFxuICAgICAgaGFzU3ByZWFkOiBmYWxzZSxcbiAgICB9O1xuICAgIGZvciAoY29uc3QgYXR0ciBvZiBub2RlLmF0dHJpYnV0ZXMgfHwgW10pIHtcbiAgICAgIGlmIChhdHRyLnR5cGUgPT09IFwiU3ByZWFkRWxlbWVudFwiIHx8IGF0dHIudHlwZSA9PT0gXCJKU1hTcHJlYWRBdHRyaWJ1dGVcIikge1xuICAgICAgICB1c2FnZS5oYXNTcHJlYWQgPSB0cnVlO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChhdHRyLnR5cGUgIT09IFwiSlNYQXR0cmlidXRlXCIpIGNvbnRpbnVlO1xuICAgICAgY29uc3QgcHJvcE5hbWUgPSBhdHRyLm5hbWU/LnZhbHVlO1xuICAgICAgaWYgKCFwcm9wTmFtZSkgY29udGludWU7XG4gICAgICBpZiAoIWF0dHIudmFsdWUpIHtcbiAgICAgICAgdXNhZ2UuYm9vbGVhblByb3BzLmFkZChwcm9wTmFtZSk7XG4gICAgICB9IGVsc2UgaWYgKGF0dHIudmFsdWUudHlwZSA9PT0gXCJTdHJpbmdMaXRlcmFsXCIpIHtcbiAgICAgICAgdXNhZ2UucHJvcHMuc2V0KHByb3BOYW1lLCBhdHRyLnZhbHVlLnZhbHVlKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHVzYWdlLnByb3BzLnNldChwcm9wTmFtZSwgXCJEWU5BTUlDXCIpO1xuICAgICAgfVxuICAgIH1cbiAgICB1c2FnZXMucHVzaCh1c2FnZSk7XG4gIH0pO1xuICByZXR1cm4gdXNhZ2VzO1xufVxuXG4vLyDilIDilIAgU2FmZWxpc3QgYnVpbGRlciDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuZnVuY3Rpb24gYnVpbGRTYWZlbGlzdHMoXG4gIGFsbFVzYWdlczogUHJvcFVzYWdlW10sXG4gIG1hbmlmZXN0OiBQdXJnZU1hbmlmZXN0LFxuKTogeyBjbGFzc1NhZmVsaXN0OiBTZXQ8c3RyaW5nPjsgYXR0clNhZmVsaXN0OiBTZXQ8c3RyaW5nPiB9IHtcbiAgY29uc3QgY2xhc3NTYWZlbGlzdCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICBjb25zdCBhdHRyU2FmZWxpc3QgPSBuZXcgU2V0PHN0cmluZz4oKTtcblxuICBjb25zdCBjb21wb25lbnRVc2FnZXMgPSBuZXcgTWFwPHN0cmluZywgUHJvcFVzYWdlW10+KCk7XG4gIGZvciAoY29uc3QgdXNhZ2Ugb2YgYWxsVXNhZ2VzKSB7XG4gICAgY29uc3QgZXhpc3RpbmcgPSBjb21wb25lbnRVc2FnZXMuZ2V0KHVzYWdlLmNvbXBvbmVudCkgPz8gW107XG4gICAgZXhpc3RpbmcucHVzaCh1c2FnZSk7XG4gICAgY29tcG9uZW50VXNhZ2VzLnNldCh1c2FnZS5jb21wb25lbnQsIGV4aXN0aW5nKTtcbiAgfVxuXG4gIGZvciAoY29uc3QgW2VudHJ5TmFtZSwgZW50cnldIG9mIE9iamVjdC5lbnRyaWVzKG1hbmlmZXN0KSkge1xuICAgIGNvbnN0IG1hdGNoaW5nVXNhZ2VzID0gZmluZE1hdGNoaW5nVXNhZ2VzKGVudHJ5TmFtZSwgY29tcG9uZW50VXNhZ2VzKTtcbiAgICBpZiAobWF0Y2hpbmdVc2FnZXMubGVuZ3RoID09PSAwKSBjb250aW51ZTtcblxuICAgIGZvciAoY29uc3QgY2xzIG9mIGVudHJ5LmNsYXNzZXMuYWx3YXlzKSBjbGFzc1NhZmVsaXN0LmFkZChjbHMpO1xuXG4gICAgZm9yIChjb25zdCBbcHJvcE9yU2xvdCwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKGVudHJ5LmNsYXNzZXMuYnlQcm9wKSkge1xuICAgICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgIGlmIChpc1Byb3BVc2VkKHByb3BPclNsb3QsIG1hdGNoaW5nVXNhZ2VzKSkge1xuICAgICAgICAgIGZvciAoY29uc3QgY2xzIG9mIHZhbHVlKSBjbGFzc1NhZmVsaXN0LmFkZChjbHMpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjb25zdCB1c2VkVmFsdWVzID0gZ2V0VXNlZEVudW1WYWx1ZXMocHJvcE9yU2xvdCwgbWF0Y2hpbmdVc2FnZXMpO1xuICAgICAgICBpZiAodXNlZFZhbHVlcyA9PT0gXCJBTExcIikge1xuICAgICAgICAgIGZvciAoY29uc3QgY2xhc3NlcyBvZiBPYmplY3QudmFsdWVzKHZhbHVlKSkge1xuICAgICAgICAgICAgZm9yIChjb25zdCBjbHMgb2YgY2xhc3NlcykgY2xhc3NTYWZlbGlzdC5hZGQoY2xzKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZm9yIChjb25zdCB2YWwgb2YgdXNlZFZhbHVlcykge1xuICAgICAgICAgICAgaWYgKHZhbHVlW3ZhbF0pIHtcbiAgICAgICAgICAgICAgZm9yIChjb25zdCBjbHMgb2YgdmFsdWVbdmFsXSkgY2xhc3NTYWZlbGlzdC5hZGQoY2xzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoZW50cnkuYXR0cnMpIHtcbiAgICAgIGZvciAoY29uc3QgW3Byb3BOYW1lLCBhdHRyTWFwXSBvZiBPYmplY3QuZW50cmllcyhlbnRyeS5hdHRycykpIHtcbiAgICAgICAgaWYgKGlzUHJvcFVzZWQocHJvcE5hbWUsIG1hdGNoaW5nVXNhZ2VzKSkge1xuICAgICAgICAgIGZvciAoY29uc3QgW2F0dHIsIHZhbF0gb2YgT2JqZWN0LmVudHJpZXMoYXR0ck1hcCkpIHtcbiAgICAgICAgICAgIGF0dHJTYWZlbGlzdC5hZGQoYFske2F0dHJ9PVwiJHt2YWx9XCJdYCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHsgY2xhc3NTYWZlbGlzdCwgYXR0clNhZmVsaXN0IH07XG59XG5cbmZ1bmN0aW9uIGZpbmRNYXRjaGluZ1VzYWdlcyhlbnRyeU5hbWU6IHN0cmluZywgdXNhZ2VNYXA6IE1hcDxzdHJpbmcsIFByb3BVc2FnZVtdPik6IFByb3BVc2FnZVtdIHtcbiAgaWYgKHVzYWdlTWFwLmhhcyhlbnRyeU5hbWUpKSByZXR1cm4gdXNhZ2VNYXAuZ2V0KGVudHJ5TmFtZSkhO1xuICBjb25zdCByZXN1bHRzOiBQcm9wVXNhZ2VbXSA9IFtdO1xuICBmb3IgKGNvbnN0IFt1c2FnZU5hbWUsIHVzYWdlc10gb2YgdXNhZ2VNYXApIHtcbiAgICBpZiAodXNhZ2VOYW1lID09PSBlbnRyeU5hbWUpIHtcbiAgICAgIHJlc3VsdHMucHVzaCguLi51c2FnZXMpO1xuICAgIH1cbiAgICBpZiAoZW50cnlOYW1lLmluY2x1ZGVzKFwiLlwiKSkge1xuICAgICAgY29uc3QgW2ZhbWlseSwgcGFydF0gPSBlbnRyeU5hbWUuc3BsaXQoXCIuXCIpO1xuICAgICAgaWYgKHBhcnQgPT09IGZhbWlseSAmJiB1c2FnZU5hbWUgPT09IGZhbWlseSkge1xuICAgICAgICByZXN1bHRzLnB1c2goLi4udXNhZ2VzKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdHM7XG59XG5cbmZ1bmN0aW9uIGlzUHJvcFVzZWQocHJvcE5hbWU6IHN0cmluZywgdXNhZ2VzOiBQcm9wVXNhZ2VbXSk6IGJvb2xlYW4ge1xuICBmb3IgKGNvbnN0IHVzYWdlIG9mIHVzYWdlcykge1xuICAgIGlmICh1c2FnZS5oYXNTcHJlYWQpIHJldHVybiB0cnVlO1xuICAgIGlmICh1c2FnZS5ib29sZWFuUHJvcHMuaGFzKHByb3BOYW1lKSkgcmV0dXJuIHRydWU7XG4gICAgaWYgKHVzYWdlLnByb3BzLmhhcyhwcm9wTmFtZSkpIHJldHVybiB0cnVlO1xuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuZnVuY3Rpb24gZ2V0VXNlZEVudW1WYWx1ZXMoc2xvdE5hbWU6IHN0cmluZywgdXNhZ2VzOiBQcm9wVXNhZ2VbXSk6IFNldDxzdHJpbmc+IHwgXCJBTExcIiB7XG4gIGNvbnN0IHZhbHVlcyA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICBmb3IgKGNvbnN0IHVzYWdlIG9mIHVzYWdlcykge1xuICAgIGlmICh1c2FnZS5oYXNTcHJlYWQpIHJldHVybiBcIkFMTFwiO1xuICAgIGNvbnN0IHZhbCA9IHVzYWdlLnByb3BzLmdldChzbG90TmFtZSk7XG4gICAgaWYgKHZhbCA9PT0gXCJEWU5BTUlDXCIpIHJldHVybiBcIkFMTFwiO1xuICAgIGlmICh2YWwgIT09IHVuZGVmaW5lZCkgdmFsdWVzLmFkZCh2YWwpO1xuICAgIGlmICh1c2FnZS5ib29sZWFuUHJvcHMuaGFzKHNsb3ROYW1lKSkgcmV0dXJuIFwiQUxMXCI7XG4gIH1cbiAgcmV0dXJuIHZhbHVlcztcbn1cblxuLy8g4pSA4pSAIENvbnN1bWVyIHNvdXJjZSBzY2FubmluZyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuYXN5bmMgZnVuY3Rpb24gc2NhbkNvbnN1bWVyU291cmNlKHNyY0Rpcjogc3RyaW5nKTogUHJvbWlzZTxQcm9wVXNhZ2VbXT4ge1xuICBjb25zdCBhbGxVc2FnZXM6IFByb3BVc2FnZVtdID0gW107XG4gIGNvbnN0IGZpbGVzID0gYXdhaXQgZmFzdEdsb2IoXCIqKi8qLnt0c3gsdHMsanN4LGpzfVwiLCB7IGN3ZDogc3JjRGlyLCBpZ25vcmU6IFtcIioqL25vZGVfbW9kdWxlcy8qKlwiXSB9KTtcblxuICBmb3IgKGNvbnN0IHJlbFBhdGggb2YgZmlsZXMpIHtcbiAgICBjb25zdCBmdWxsUGF0aCA9IHBhdGguam9pbihzcmNEaXIsIHJlbFBhdGgpO1xuICAgIGNvbnN0IGNvZGUgPSBhd2FpdCBub2RlUmVhZEZpbGUoZnVsbFBhdGgsIFwidXRmLThcIik7XG4gICAgaWYgKCFjb2RlLmluY2x1ZGVzKFwiQHBhdGhzY2FsZS91aVwiKSkgY29udGludWU7XG5cbiAgICBjb25zdCBpc1RzeCA9IC9cXC5bdGpdc3gkLy50ZXN0KHJlbFBhdGgpO1xuICAgIGNvbnN0IGFzdCA9IGF3YWl0IHN3Yy5wYXJzZShjb2RlLCB7IHN5bnRheDogXCJ0eXBlc2NyaXB0XCIsIHRzeDogaXNUc3ggfSk7XG4gICAgY29uc3QgdWlJbXBvcnRzID0gZXh0cmFjdFVJSW1wb3J0cyhhc3QpO1xuICAgIGlmICh1aUltcG9ydHMuc2l6ZSA9PT0gMCkgY29udGludWU7XG5cbiAgICBhbGxVc2FnZXMucHVzaCguLi5leHRyYWN0SlNYVXNhZ2VzKGFzdCwgdWlJbXBvcnRzKSk7XG4gIH1cblxuICByZXR1cm4gYWxsVXNhZ2VzO1xufVxuXG4vLyDilIDilIAgTGV2ZWwgMjogYXR0cmlidXRlIHB1cmdlIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5mdW5jdGlvbiBwdXJnZUF0dHJpYnV0ZXMoY3NzOiBzdHJpbmcsIGF0dHJTYWZlbGlzdDogU2V0PHN0cmluZz4pOiBzdHJpbmcge1xuICBjb25zdCByb290ID0gcG9zdGNzcy5wYXJzZShjc3MpO1xuXG4gIHJvb3Qud2Fsa1J1bGVzKChydWxlKSA9PiB7XG4gICAgY29uc3Qgc2VsZWN0b3JzID0gcnVsZS5zZWxlY3RvcnM7XG4gICAgY29uc3Qga2VwdDogc3RyaW5nW10gPSBbXTtcblxuICAgIGZvciAoY29uc3Qgc2VsIG9mIHNlbGVjdG9ycykge1xuICAgICAgY29uc3QgYXR0ck1hdGNoZXMgPSBzZWwubWF0Y2hBbGwoL1xcWyhkYXRhLVthLXotXSt8YXJpYS1bYS16LV0rKT1cIihbXlwiXSspXCJcXF0vZyk7XG4gICAgICBsZXQgc2hvdWxkS2VlcCA9IHRydWU7XG5cbiAgICAgIGZvciAoY29uc3QgbWF0Y2ggb2YgYXR0ck1hdGNoZXMpIHtcbiAgICAgICAgY29uc3QgYXR0clNlbGVjdG9yID0gYFske21hdGNoWzFdfT1cIiR7bWF0Y2hbMl19XCJdYDtcbiAgICAgICAgaWYgKG1hdGNoWzFdID09PSBcImRhdGEtc2xvdFwiKSBjb250aW51ZTtcbiAgICAgICAgaWYgKCFhdHRyU2FmZWxpc3QuaGFzKGF0dHJTZWxlY3RvcikpIHtcbiAgICAgICAgICBzaG91bGRLZWVwID0gZmFsc2U7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKHNob3VsZEtlZXApIGtlcHQucHVzaChzZWwpO1xuICAgIH1cblxuICAgIGlmIChrZXB0Lmxlbmd0aCA9PT0gMCkge1xuICAgICAgcnVsZS5yZW1vdmUoKTtcbiAgICB9IGVsc2UgaWYgKGtlcHQubGVuZ3RoIDwgc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgcnVsZS5zZWxlY3RvcnMgPSBrZXB0O1xuICAgIH1cbiAgfSk7XG5cbiAgcmV0dXJuIHJvb3QudG9TdHJpbmcoKTtcbn1cblxuLy8g4pSA4pSAIExldmVsIDM6IHVudXNlZCBDU1MgdmFyaWFibGUgY2xlYW51cCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuZnVuY3Rpb24gY2xlYW5VbnVzZWRWYXJzKGNzczogc3RyaW5nKTogc3RyaW5nIHtcbiAgbGV0IGNoYW5nZWQgPSB0cnVlO1xuICBsZXQgcmVzdWx0ID0gY3NzO1xuXG4gIHdoaWxlIChjaGFuZ2VkKSB7XG4gICAgY2hhbmdlZCA9IGZhbHNlO1xuICAgIGNvbnN0IHJvb3QgPSBwb3N0Y3NzLnBhcnNlKHJlc3VsdCk7XG5cbiAgICBjb25zdCBkZWNsYXJlZCA9IG5ldyBNYXA8c3RyaW5nLCB7IHJ1bGU6IFJ1bGUgfCBBdFJ1bGU7IHByb3A6IHN0cmluZzsgaW5kZXg6IG51bWJlciB9W10+KCk7XG4gICAgcm9vdC53YWxrRGVjbHMoL14tLS8sIChkZWNsKSA9PiB7XG4gICAgICBjb25zdCBlbnRyaWVzID0gZGVjbGFyZWQuZ2V0KGRlY2wucHJvcCkgPz8gW107XG4gICAgICBlbnRyaWVzLnB1c2goeyBydWxlOiBkZWNsLnBhcmVudCBhcyBSdWxlLCBwcm9wOiBkZWNsLnByb3AsIGluZGV4OiBlbnRyaWVzLmxlbmd0aCB9KTtcbiAgICAgIGRlY2xhcmVkLnNldChkZWNsLnByb3AsIGVudHJpZXMpO1xuICAgIH0pO1xuXG4gICAgY29uc3QgcmVmZXJlbmNlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICAgIHJvb3Qud2Fsa0RlY2xzKChkZWNsKSA9PiB7XG4gICAgICBjb25zdCByZWZzID0gZGVjbC52YWx1ZS5tYXRjaEFsbCgvdmFyXFwoXFxzKigtLVthLXpBLVowLTlfLV0rKS9nKTtcbiAgICAgIGZvciAoY29uc3QgcmVmIG9mIHJlZnMpIHtcbiAgICAgICAgcmVmZXJlbmNlZC5hZGQocmVmWzFdKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGZvciAoY29uc3QgW3Zhck5hbWUsIGVudHJpZXNdIG9mIGRlY2xhcmVkKSB7XG4gICAgICBpZiAoIXJlZmVyZW5jZWQuaGFzKHZhck5hbWUpKSB7XG4gICAgICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgICAgIGVudHJ5LnJ1bGUud2Fsa0RlY2xzKGVudHJ5LnByb3AsIChkZWNsKSA9PiB7XG4gICAgICAgICAgICBkZWNsLnJlbW92ZSgpO1xuICAgICAgICAgICAgY2hhbmdlZCA9IHRydWU7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByb290LndhbGtSdWxlcygocnVsZSkgPT4ge1xuICAgICAgaWYgKHJ1bGUubm9kZXMgJiYgcnVsZS5ub2Rlcy5sZW5ndGggPT09IDApIHJ1bGUucmVtb3ZlKCk7XG4gICAgfSk7XG4gICAgcm9vdC53YWxrQXRSdWxlcygoYXRSdWxlKSA9PiB7XG4gICAgICBpZiAoYXRSdWxlLm5vZGVzICYmIGF0UnVsZS5ub2Rlcy5sZW5ndGggPT09IDApIGF0UnVsZS5yZW1vdmUoKTtcbiAgICB9KTtcblxuICAgIHJlc3VsdCA9IHJvb3QudG9TdHJpbmcoKTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbi8vIOKUgOKUgCBUaGUgcnNidWlsZCBwbHVnaW4g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmV4cG9ydCBjb25zdCBwbHVnaW5Dc3NQdXJnZSA9IChvcHRpb25zOiBDc3NQdXJnZU9wdGlvbnMpOiBSc2J1aWxkUGx1Z2luID0+ICh7XG4gIG5hbWU6IFwicGx1Z2luLWNzcy1wdXJnZVwiLFxuXG4gIHNldHVwKGFwaSkge1xuICAgIGNvbnN0IHtcbiAgICAgIG1hbmlmZXN0OiBtYW5pZmVzdFBhdGgsXG4gICAgICBzcmNEaXIgPSBcInNyY1wiLFxuICAgICAgYXR0clB1cmdlID0gdHJ1ZSxcbiAgICAgIGNsZWFuVmFycyA9IHRydWUsXG4gICAgICB2ZXJib3NlID0gdHJ1ZSxcbiAgICB9ID0gb3B0aW9ucztcblxuICAgIGFwaS5wcm9jZXNzQXNzZXRzKFxuICAgICAgeyBzdGFnZTogXCJvcHRpbWl6ZS1zaXplXCIgfSxcbiAgICAgIGFzeW5jICh7IGFzc2V0cywgc291cmNlcywgY29tcGlsYXRpb24gfSkgPT4ge1xuICAgICAgICBjb25zdCBsb2cgPSB2ZXJib3NlID8gY29uc29sZS5sb2cuYmluZChjb25zb2xlKSA6ICgpID0+IHt9O1xuXG4gICAgICAgIC8vIDEuIExvYWQgbWFuaWZlc3RcbiAgICAgICAgY29uc3QgbWFuaWZlc3Q6IFB1cmdlTWFuaWZlc3QgPSBKU09OLnBhcnNlKFxuICAgICAgICAgIGF3YWl0IG5vZGVSZWFkRmlsZShwYXRoLnJlc29sdmUobWFuaWZlc3RQYXRoKSwgXCJ1dGYtOFwiKSxcbiAgICAgICAgKTtcbiAgICAgICAgbG9nKGBbY3NzLXB1cmdlXSBNYW5pZmVzdCBsb2FkZWQ6ICR7T2JqZWN0LmtleXMobWFuaWZlc3QpLmxlbmd0aH0gZW50cmllc2ApO1xuXG4gICAgICAgIC8vIDIuIFNjYW4gY29uc3VtZXIgc291cmNlXG4gICAgICAgIGNvbnN0IHJlc29sdmVkU3JjID0gcGF0aC5yZXNvbHZlKHNyY0Rpcik7XG4gICAgICAgIGNvbnN0IHVzYWdlcyA9IGF3YWl0IHNjYW5Db25zdW1lclNvdXJjZShyZXNvbHZlZFNyYyk7XG4gICAgICAgIGxvZyhgW2Nzcy1wdXJnZV0gU2Nhbm5lZCAke3Jlc29sdmVkU3JjfTogJHt1c2FnZXMubGVuZ3RofSBjb21wb25lbnQgdXNhZ2VzYCk7XG5cbiAgICAgICAgLy8gMy4gQnVpbGQgc2FmZWxpc3RzXG4gICAgICAgIGNvbnN0IHsgY2xhc3NTYWZlbGlzdCwgYXR0clNhZmVsaXN0IH0gPSBidWlsZFNhZmVsaXN0cyh1c2FnZXMsIG1hbmlmZXN0KTtcbiAgICAgICAgbG9nKGBbY3NzLXB1cmdlXSBTYWZlbGlzdDogJHtjbGFzc1NhZmVsaXN0LnNpemV9IGNsYXNzZXMsICR7YXR0clNhZmVsaXN0LnNpemV9IGF0dHJzYCk7XG5cbiAgICAgICAgLy8gNC4gUHJvY2VzcyBlYWNoIENTUyBhc3NldFxuICAgICAgICBmb3IgKGNvbnN0IFtuYW1lLCBhc3NldF0gb2YgT2JqZWN0LmVudHJpZXMoYXNzZXRzKSkge1xuICAgICAgICAgIGlmICghbmFtZS5lbmRzV2l0aChcIi5jc3NcIikpIGNvbnRpbnVlO1xuXG4gICAgICAgICAgY29uc3Qgb3JpZ2luYWxDc3MgPSBhc3NldC5zb3VyY2UoKS50b1N0cmluZygpO1xuICAgICAgICAgIGNvbnN0IG9yaWdpbmFsU2l6ZSA9IEJ1ZmZlci5ieXRlTGVuZ3RoKG9yaWdpbmFsQ3NzLCBcInV0Zi04XCIpO1xuICAgICAgICAgIGxvZyhgW2Nzcy1wdXJnZV0gUHJvY2Vzc2luZyAke25hbWV9ICgkeyhvcmlnaW5hbFNpemUgLyAxMDI0KS50b0ZpeGVkKDEpfSBLQilgKTtcblxuICAgICAgICAgIC8vIExldmVsIDE6IGNsYXNzLWxldmVsIHB1cmdlXG4gICAgICAgICAgY29uc3QgcHVyZ2VSZXN1bHQgPSBhd2FpdCBuZXcgUHVyZ2VDU1MoKS5wdXJnZSh7XG4gICAgICAgICAgICBjb250ZW50OiBbXSxcbiAgICAgICAgICAgIGNzczogW3sgcmF3OiBvcmlnaW5hbENzcyB9XSxcbiAgICAgICAgICAgIHNhZmVsaXN0OiBbLi4uY2xhc3NTYWZlbGlzdF0sXG4gICAgICAgICAgICBrZXlmcmFtZXM6IGZhbHNlLFxuICAgICAgICAgICAgZm9udEZhY2U6IGZhbHNlLFxuICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgbGV0IHB1cmdlZENzcyA9IHB1cmdlUmVzdWx0WzBdPy5jc3MgPz8gb3JpZ2luYWxDc3M7XG4gICAgICAgICAgY29uc3QgYWZ0ZXJMMSA9IEJ1ZmZlci5ieXRlTGVuZ3RoKHB1cmdlZENzcywgXCJ1dGYtOFwiKTtcbiAgICAgICAgICBsb2coYFtjc3MtcHVyZ2VdICAgTDEgY2xhc3MgcHVyZ2U6ICR7KG9yaWdpbmFsU2l6ZSAvIDEwMjQpLnRvRml4ZWQoMSl9IOKGkiAkeyhhZnRlckwxIC8gMTAyNCkudG9GaXhlZCgxKX0gS0JgKTtcblxuICAgICAgICAgIC8vIExldmVsIDI6IGF0dHJpYnV0ZS1sZXZlbCBwdXJnZVxuICAgICAgICAgIGlmIChhdHRyUHVyZ2UgJiYgYXR0clNhZmVsaXN0LnNpemUgPiAwKSB7XG4gICAgICAgICAgICBwdXJnZWRDc3MgPSBwdXJnZUF0dHJpYnV0ZXMocHVyZ2VkQ3NzLCBhdHRyU2FmZWxpc3QpO1xuICAgICAgICAgICAgY29uc3QgYWZ0ZXJMMiA9IEJ1ZmZlci5ieXRlTGVuZ3RoKHB1cmdlZENzcywgXCJ1dGYtOFwiKTtcbiAgICAgICAgICAgIGxvZyhgW2Nzcy1wdXJnZV0gICBMMiBhdHRyIHB1cmdlOiAkeyhhZnRlckwxIC8gMTAyNCkudG9GaXhlZCgxKX0g4oaSICR7KGFmdGVyTDIgLyAxMDI0KS50b0ZpeGVkKDEpfSBLQmApO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIExldmVsIDM6IHVudXNlZCBDU1MgdmFyaWFibGUgY2xlYW51cFxuICAgICAgICAgIGlmIChjbGVhblZhcnMpIHtcbiAgICAgICAgICAgIHB1cmdlZENzcyA9IGNsZWFuVW51c2VkVmFycyhwdXJnZWRDc3MpO1xuICAgICAgICAgICAgY29uc3QgYWZ0ZXJMMyA9IEJ1ZmZlci5ieXRlTGVuZ3RoKHB1cmdlZENzcywgXCJ1dGYtOFwiKTtcbiAgICAgICAgICAgIGxvZyhgW2Nzcy1wdXJnZV0gICBMMyB2YXIgY2xlYW51cDog4oaSICR7KGFmdGVyTDMgLyAxMDI0KS50b0ZpeGVkKDEpfSBLQmApO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGNvbnN0IGZpbmFsU2l6ZSA9IEJ1ZmZlci5ieXRlTGVuZ3RoKHB1cmdlZENzcywgXCJ1dGYtOFwiKTtcbiAgICAgICAgICBsb2coYFtjc3MtcHVyZ2VdICAgRmluYWw6ICR7KG9yaWdpbmFsU2l6ZSAvIDEwMjQpLnRvRml4ZWQoMSl9IOKGkiAkeyhmaW5hbFNpemUgLyAxMDI0KS50b0ZpeGVkKDEpfSBLQiAoJHsoKDEgLSBmaW5hbFNpemUgLyBvcmlnaW5hbFNpemUpICogMTAwKS50b0ZpeGVkKDEpfSUgcmVkdWN0aW9uKWApO1xuXG4gICAgICAgICAgLy8gV3JpdGUgYmFja1xuICAgICAgICAgIGNvbnN0IHNvdXJjZSA9IG5ldyBzb3VyY2VzLlJhd1NvdXJjZShwdXJnZWRDc3MpO1xuICAgICAgICAgIGNvbXBpbGF0aW9uLnVwZGF0ZUFzc2V0KG5hbWUsIHNvdXJjZSk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgKTtcbiAgfSxcbn0pO1xuIgogIF0sCiAgIm1hcHBpbmdzIjogIjtBQWdCQTtBQUNBO0FBRUE7QUFDQTtBQUNBLHFCQUFTO0FBQ1QsaUJBQVM7QUFzQ1QsU0FBUyxPQUFPLENBQUMsTUFBVyxTQUE4QjtBQUFBLEVBQ3hELElBQUksQ0FBQyxRQUFRLE9BQU8sU0FBUztBQUFBLElBQVU7QUFBQSxFQUN2QyxRQUFRLElBQUk7QUFBQSxFQUNaLFdBQVcsT0FBTyxPQUFPLEtBQUssSUFBSSxHQUFHO0FBQUEsSUFDbkMsSUFBSSxRQUFRO0FBQUEsTUFBUTtBQUFBLElBQ3BCLE1BQU0sTUFBTSxLQUFLO0FBQUEsSUFDakIsSUFBSSxNQUFNLFFBQVEsR0FBRyxHQUFHO0FBQUEsTUFDdEIsV0FBVyxRQUFRO0FBQUEsUUFBSyxRQUFRLE1BQU0sT0FBTztBQUFBLElBQy9DLEVBQU8sU0FBSSxPQUFPLE9BQU8sUUFBUSxVQUFVO0FBQUEsTUFDekMsUUFBUSxLQUFLLE9BQU87QUFBQSxJQUN0QjtBQUFBLEVBQ0Y7QUFBQTtBQUdGLFNBQVMsZ0JBQWdCLENBQUMsS0FBK0I7QUFBQSxFQUN2RCxNQUFNLFVBQVUsSUFBSTtBQUFBLEVBQ3BCLFdBQVcsUUFBUSxJQUFJLE1BQU07QUFBQSxJ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QU87QUFBQSxRQUN2QyxNQUFNLFVBQVUsT0FBTyxXQUFXLFdBQVcsT0FBTztBQUFBLFFBQ3BELElBQUksa0NBQWtDLGVBQWUsTUFBTSxRQUFRLENBQUMsUUFBTyxVQUFVLE1BQU0sUUFBUSxDQUFDLE1BQU07QUFBQSxRQUcxRyxJQUFJLGFBQWEsYUFBYSxPQUFPLEdBQUc7QUFBQSxVQUN0QyxZQUFZLGdCQUFnQixXQUFXLFlBQVk7QUFBQSxVQUNuRCxNQUFNLFVBQVUsT0FBTyxXQUFXLFdBQVcsT0FBTztBQUFBLFVBQ3BELElBQUksaUNBQWlDLFVBQVUsTUFBTSxRQUFRLENBQUMsUUFBTyxVQUFVLE1BQU0sUUFBUSxDQUFDLE1BQU07QUFBQSxRQUN0RztBQUFBLFFBR0EsSUFBSSxXQUFXO0FBQUEsVUFDYixZQUFZLGdCQUFnQixTQUFTO0FBQUEsVUFDckMsTUFBTSxVQUFVLE9BQU8sV0FBVyxXQUFXLE9BQU87QUFBQSxVQUNwRCxJQUFJLG9DQUFtQyxVQUFVLE1BQU0sUUFBUSxDQUFDLE1BQU07QUFBQSxRQUN4RTtBQUFBLFFBRUEsTUFBTSxZQUFZLE9BQU8sV0FBVyxXQUFXLE9BQU87QUFBQSxRQUN0RCxJQUFJLHlCQUF5QixlQUFlLE1BQU0sUUFBUSxDQUFDLFFBQU8sWUFBWSxNQUFNLFFBQVEsQ0FBQyxXQUFXLElBQUksWUFBWSxnQkFBZ0IsS0FBSyxRQUFRLENBQUMsZUFBZTtBQUFBLFFBR3JLLE1BQU0sU0FBUyxJQUFJLFFBQVEsVUFBVSxTQUFTO0FBQUEsUUFDOUMsWUFBWSxZQUFZLE1BQU0sTUFBTTtBQUFBLE1BQ3RDO0FBQUEsS0FFSjtBQUFBO0FBRUo7IiwKICAiZGVidWdJZCI6ICJDQjVCMjA5MzA2MzY2M0U4NjQ3NTZFMjE2NDc1NkUyMSIsCiAgIm5hbWVzIjogW10KfQ==
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rsbuild-plugin-css-purge
|
|
3
|
+
*
|
|
4
|
+
* Two-level CSS purge for @pathscale/ui consumers.
|
|
5
|
+
*
|
|
6
|
+
* Level 1: class-level purge via purgecss — removes entire rules whose selectors
|
|
7
|
+
* don't match the safelist built from consumer JSX analysis.
|
|
8
|
+
* Level 2: attribute-level purge — within kept rules, strips compound selectors
|
|
9
|
+
* containing data-attr / aria-attr attribute selectors not in the attr safelist.
|
|
10
|
+
*
|
|
11
|
+
* Usage in rsbuild.config.ts:
|
|
12
|
+
* import { pluginCssPurge } from "@pathscale/rebuild-plugin-ui-css-purge";
|
|
13
|
+
* export default defineConfig({ plugins: [pluginCssPurge({ manifest: "..." })] });
|
|
14
|
+
*/
|
|
15
|
+
import type { RsbuildPlugin } from "@rsbuild/core";
|
|
16
|
+
export interface CssPurgeOptions {
|
|
17
|
+
/** Path to purge-manifest.json (generated by generate-manifest.ts) */
|
|
18
|
+
manifest: string;
|
|
19
|
+
/** Consumer source directory to scan for JSX usage (default: "src") */
|
|
20
|
+
srcDir?: string;
|
|
21
|
+
/** Enable Level 2 attribute purge (default: true) */
|
|
22
|
+
attrPurge?: boolean;
|
|
23
|
+
/** Enable CSS variable cleanup (default: true) */
|
|
24
|
+
cleanVars?: boolean;
|
|
25
|
+
/** Log purge stats (default: true) */
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const pluginCssPurge: (options: CssPurgeOptions) => RsbuildPlugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer-side JSX scanner.
|
|
3
|
+
*
|
|
4
|
+
* Walks a consumer's source tree, finds component imports from @pathscale/ui,
|
|
5
|
+
* collects prop values, and cross-references with the purge manifest to build
|
|
6
|
+
* Level 1 (class) and Level 2 (attribute) safelists.
|
|
7
|
+
*
|
|
8
|
+
* Usage: bun run src/scan-consumer.ts <consumer-src-dir> <purge-manifest.json>
|
|
9
|
+
*/
|
|
10
|
+
interface ComponentManifest {
|
|
11
|
+
classes: {
|
|
12
|
+
always: string[];
|
|
13
|
+
byProp: Record<string, string[] | Record<string, string[]>>;
|
|
14
|
+
};
|
|
15
|
+
attrs?: Record<string, Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
type PurgeManifest = Record<string, ComponentManifest>;
|
|
18
|
+
/** What we collect per component usage from JSX */
|
|
19
|
+
interface PropUsage {
|
|
20
|
+
component: string;
|
|
21
|
+
props: Map<string, string | "DYNAMIC">;
|
|
22
|
+
booleanProps: Set<string>;
|
|
23
|
+
hasSpread: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Extract @pathscale/ui imports from a parsed module */
|
|
26
|
+
declare function extractUIImports(ast: any): Map<string, string>;
|
|
27
|
+
/** Extract JSX usages of UI components */
|
|
28
|
+
declare function extractJSXUsages(ast: any, uiComponents: Map<string, string>): PropUsage[];
|
|
29
|
+
interface Safelists {
|
|
30
|
+
classSafelist: Set<string>;
|
|
31
|
+
attrSafelist: Set<string>;
|
|
32
|
+
}
|
|
33
|
+
declare function buildSafelists(allUsages: PropUsage[], manifest: PurgeManifest): Safelists;
|
|
34
|
+
export { extractUIImports, extractJSXUsages, buildSafelists };
|
|
35
|
+
export type { PropUsage, PurgeManifest, ComponentManifest, Safelists };
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pathscale/rebuild-plugin-ui-css-purge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"generate-manifest": "src/generate-manifest.ts"
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist", "src/generate-manifest.ts"],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@swc/core": "^1.15.24",
|
|
19
|
+
"fast-glob": "^3.3.3",
|
|
20
|
+
"postcss": "^8.5.9",
|
|
21
|
+
"purgecss": "^8.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@rsbuild/core": "^1.7.5"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "bun run build.ts",
|
|
28
|
+
"lint": "biome check .",
|
|
29
|
+
"format": "biome format . --write"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@rsbuild/core": "^1.7.5",
|
|
33
|
+
"bun-types": "^1.3.12"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lib-side database generator.
|
|
3
|
+
*
|
|
4
|
+
* Reads all `*.classnames.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
|
+
|
|
12
|
+
import { Glob } from "bun";
|
|
13
|
+
import path from "path";
|
|
14
|
+
|
|
15
|
+
// ── Types ──────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
type ClassValue = string | readonly string[];
|
|
18
|
+
|
|
19
|
+
interface ComponentManifest {
|
|
20
|
+
classes: {
|
|
21
|
+
always: string[];
|
|
22
|
+
byProp: Record<string, string[] | Record<string, string[]>>;
|
|
23
|
+
};
|
|
24
|
+
attrs?: Record<string, Record<string, string>>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type PurgeManifest = Record<string, ComponentManifest>;
|
|
28
|
+
|
|
29
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/** Flatten a class value (string or string[]) into an array of individual class names. */
|
|
32
|
+
function flattenClasses(val: ClassValue): string[] {
|
|
33
|
+
if (typeof val === "string") {
|
|
34
|
+
return val.split(/\s+/).filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
return (val as readonly string[]).flatMap((s) => s.split(/\s+/).filter(Boolean));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Check if a value is a plain object (not array, not null). */
|
|
40
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
41
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Walk a single CLASSES object (one component or one part of a compound component)
|
|
46
|
+
* and produce the manifest entry.
|
|
47
|
+
*/
|
|
48
|
+
function walkClassesObject(obj: Record<string, unknown>): ComponentManifest {
|
|
49
|
+
const always: string[] = [];
|
|
50
|
+
const byProp: Record<string, string[] | Record<string, string[]>> = {};
|
|
51
|
+
let attrs: Record<string, Record<string, string>> | undefined;
|
|
52
|
+
|
|
53
|
+
for (const [slot, value] of Object.entries(obj)) {
|
|
54
|
+
if (slot === "base") {
|
|
55
|
+
always.push(...flattenClasses(value as ClassValue));
|
|
56
|
+
} else if (slot === "attrs") {
|
|
57
|
+
if (isRecord(value)) {
|
|
58
|
+
attrs = {};
|
|
59
|
+
for (const [propName, attrMap] of Object.entries(value)) {
|
|
60
|
+
if (isRecord(attrMap)) {
|
|
61
|
+
attrs[propName] = attrMap as Record<string, string>;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (slot === "flag") {
|
|
66
|
+
if (isRecord(value)) {
|
|
67
|
+
for (const [propName, classVal] of Object.entries(value)) {
|
|
68
|
+
byProp[propName] = flattenClasses(classVal as ClassValue);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (isRecord(value)) {
|
|
72
|
+
const enumMap: Record<string, string[]> = {};
|
|
73
|
+
for (const [enumKey, classVal] of Object.entries(value)) {
|
|
74
|
+
enumMap[enumKey] = flattenClasses(classVal as ClassValue);
|
|
75
|
+
}
|
|
76
|
+
byProp[slot] = enumMap;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const manifest: ComponentManifest = { classes: { always, byProp } };
|
|
81
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
82
|
+
manifest.attrs = attrs;
|
|
83
|
+
}
|
|
84
|
+
return manifest;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect whether CLASSES is compound (top-level keys are part names like Root, Item)
|
|
89
|
+
* or flat (top-level keys are slots like base, variant, flag).
|
|
90
|
+
*/
|
|
91
|
+
const KNOWN_SLOTS = new Set(["base", "variant", "size", "flag", "color", "attrs"]);
|
|
92
|
+
|
|
93
|
+
function isCompound(obj: Record<string, unknown>): boolean {
|
|
94
|
+
for (const key of Object.keys(obj)) {
|
|
95
|
+
if (KNOWN_SLOTS.has(key)) return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Main ───────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
async function main() {
|
|
103
|
+
const args = process.argv.slice(2);
|
|
104
|
+
let componentsDir = args[0];
|
|
105
|
+
let outPath = "purge-manifest.json";
|
|
106
|
+
|
|
107
|
+
const outIdx = args.indexOf("--out");
|
|
108
|
+
if (outIdx !== -1 && args[outIdx + 1]) {
|
|
109
|
+
outPath = args[outIdx + 1];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!componentsDir) {
|
|
113
|
+
console.error("Usage: bun run src/generate-manifest.ts <path-to-components-dir> [--out <path>]");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
componentsDir = path.resolve(componentsDir);
|
|
118
|
+
console.log(`Scanning ${componentsDir} for *.classnames.ts files…`);
|
|
119
|
+
|
|
120
|
+
const manifest: PurgeManifest = {};
|
|
121
|
+
const glob = new Glob("**/*.classnames.ts");
|
|
122
|
+
|
|
123
|
+
for await (const relPath of glob.scan({ cwd: componentsDir })) {
|
|
124
|
+
const fullPath = path.join(componentsDir, relPath);
|
|
125
|
+
|
|
126
|
+
const mod = await import(fullPath);
|
|
127
|
+
const CLASSES = mod.CLASSES;
|
|
128
|
+
|
|
129
|
+
if (!CLASSES || typeof CLASSES !== "object") {
|
|
130
|
+
console.warn(` SKIP ${relPath} — no CLASSES export found`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fileName = path.basename(relPath, ".classnames.ts");
|
|
135
|
+
|
|
136
|
+
if (isCompound(CLASSES as Record<string, unknown>)) {
|
|
137
|
+
for (const [partName, partObj] of Object.entries(CLASSES as Record<string, unknown>)) {
|
|
138
|
+
if (isRecord(partObj)) {
|
|
139
|
+
const entryName = `${fileName}.${partName}`;
|
|
140
|
+
manifest[entryName] = walkClassesObject(partObj as Record<string, unknown>);
|
|
141
|
+
console.log(` ✓ ${entryName}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
manifest[fileName] = walkClassesObject(CLASSES as Record<string, unknown>);
|
|
146
|
+
console.log(` ✓ ${fileName}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const json = JSON.stringify(manifest, null, 2);
|
|
151
|
+
await Bun.write(outPath, json);
|
|
152
|
+
console.log(`\nWrote ${outPath} (${Object.keys(manifest).length} entries, ${json.length} bytes)`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
main();
|