frameshot-mcp 0.9.7 → 0.10.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/dist/{chunk-VUYZHZBH.js → chunk-6OTGZDUU.js} +1 -1
- package/dist/{chunk-MEBQ7ZWA.js → chunk-WACVDBUF.js} +67 -29
- package/dist/cli.js +27 -42
- package/dist/index.js +17 -8
- package/dist/renderer.d.ts +14 -0
- package/dist/renderer.js +1 -1
- package/package.json +7 -1
- package/scripts/render-changed.mjs +8 -9
|
@@ -264,6 +264,7 @@ var SnapshotStore = class {
|
|
|
264
264
|
// src/infrastructure/project-detector.ts
|
|
265
265
|
import { existsSync, readFileSync } from "fs";
|
|
266
266
|
import { dirname, join, resolve } from "path";
|
|
267
|
+
import { parseTsconfig } from "get-tsconfig";
|
|
267
268
|
var VITE_CONFIG_NAMES = [
|
|
268
269
|
"vite.config.ts",
|
|
269
270
|
"vite.config.js",
|
|
@@ -274,9 +275,10 @@ var ProjectDetector = class {
|
|
|
274
275
|
detect(filePath) {
|
|
275
276
|
const root = this.findProjectRoot(filePath);
|
|
276
277
|
const viteConfigPath = this.findViteConfig(root);
|
|
277
|
-
const
|
|
278
|
+
const pkg = this.readPackageJson(root);
|
|
279
|
+
const framework = this.detectFramework(pkg);
|
|
278
280
|
const hasVite = this.checkViteAvailable(root);
|
|
279
|
-
const isNextJs = this.checkIsNextJs(
|
|
281
|
+
const isNextJs = this.checkIsNextJs(pkg);
|
|
280
282
|
const pathAliases = this.readTsconfigAliases(root);
|
|
281
283
|
return { root, viteConfigPath, framework, hasVite, isNextJs, pathAliases };
|
|
282
284
|
}
|
|
@@ -299,48 +301,41 @@ var ProjectDetector = class {
|
|
|
299
301
|
}
|
|
300
302
|
return void 0;
|
|
301
303
|
}
|
|
302
|
-
|
|
304
|
+
readPackageJson(root) {
|
|
303
305
|
const pkgPath = join(root, "package.json");
|
|
304
|
-
if (!existsSync(pkgPath)) return
|
|
306
|
+
if (!existsSync(pkgPath)) return null;
|
|
305
307
|
try {
|
|
306
|
-
|
|
307
|
-
const allDeps = {
|
|
308
|
-
...pkg.dependencies,
|
|
309
|
-
...pkg.devDependencies
|
|
310
|
-
};
|
|
311
|
-
if (allDeps["solid-js"]) return "solid";
|
|
312
|
-
if (allDeps.preact) return "preact";
|
|
313
|
-
if (allDeps.react || allDeps["react-dom"]) return "react";
|
|
314
|
-
if (allDeps.vue) return "vue";
|
|
315
|
-
if (allDeps.svelte) return "svelte";
|
|
308
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
316
309
|
} catch {
|
|
310
|
+
return null;
|
|
317
311
|
}
|
|
312
|
+
}
|
|
313
|
+
detectFramework(pkg) {
|
|
314
|
+
if (!pkg) return "html";
|
|
315
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
316
|
+
if (allDeps["solid-js"]) return "solid";
|
|
317
|
+
if (allDeps.preact) return "preact";
|
|
318
|
+
if (allDeps.react || allDeps["react-dom"]) return "react";
|
|
319
|
+
if (allDeps.vue) return "vue";
|
|
320
|
+
if (allDeps.svelte) return "svelte";
|
|
318
321
|
return "html";
|
|
319
322
|
}
|
|
320
323
|
checkViteAvailable(root) {
|
|
321
324
|
const vitePkgPath = join(root, "node_modules", "vite", "package.json");
|
|
322
325
|
return existsSync(vitePkgPath);
|
|
323
326
|
}
|
|
324
|
-
checkIsNextJs(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
329
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
330
|
-
return !!allDeps.next;
|
|
331
|
-
} catch {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
327
|
+
checkIsNextJs(pkg) {
|
|
328
|
+
if (!pkg) return false;
|
|
329
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
330
|
+
return !!allDeps.next;
|
|
334
331
|
}
|
|
335
332
|
readTsconfigAliases(root) {
|
|
336
333
|
const tscPath = join(root, "tsconfig.json");
|
|
337
334
|
if (!existsSync(tscPath)) return {};
|
|
338
335
|
try {
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
const paths = tsc.compilerOptions?.paths ?? {};
|
|
343
|
-
const baseUrl = tsc.compilerOptions?.baseUrl ?? ".";
|
|
336
|
+
const tsconfig = parseTsconfig(tscPath);
|
|
337
|
+
const paths = tsconfig.compilerOptions?.paths ?? {};
|
|
338
|
+
const baseUrl = tsconfig.compilerOptions?.baseUrl ?? ".";
|
|
344
339
|
const result = {};
|
|
345
340
|
for (const [alias, targets] of Object.entries(paths)) {
|
|
346
341
|
const key = alias.replace(/\/\*$/, "");
|
|
@@ -1378,6 +1373,7 @@ import { readFileSync as readFileSync3 } from "fs";
|
|
|
1378
1373
|
import { extname as extname2 } from "path";
|
|
1379
1374
|
|
|
1380
1375
|
// src/infrastructure/page-utils.ts
|
|
1376
|
+
import picomatch from "picomatch";
|
|
1381
1377
|
function attachConsoleCapture(page) {
|
|
1382
1378
|
const errors = [];
|
|
1383
1379
|
const onMessage = (msg) => {
|
|
@@ -1394,6 +1390,46 @@ function attachConsoleCapture(page) {
|
|
|
1394
1390
|
}
|
|
1395
1391
|
};
|
|
1396
1392
|
}
|
|
1393
|
+
function normalizeResponse(value) {
|
|
1394
|
+
if (value && typeof value === "object" && "body" in value) {
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
return { body: value };
|
|
1398
|
+
}
|
|
1399
|
+
async function attachMocks(page, mocks) {
|
|
1400
|
+
const matchers = Object.entries(mocks).map(([pattern, value]) => ({
|
|
1401
|
+
pattern,
|
|
1402
|
+
isMatch: pattern.startsWith("http") ? (url) => url === pattern : picomatch(pattern, { dot: true }),
|
|
1403
|
+
response: normalizeResponse(value)
|
|
1404
|
+
}));
|
|
1405
|
+
if (matchers.length === 0) return;
|
|
1406
|
+
await page.route("**/*", async (route) => {
|
|
1407
|
+
const url = route.request().url();
|
|
1408
|
+
const pathname = (() => {
|
|
1409
|
+
try {
|
|
1410
|
+
return new URL(url).pathname;
|
|
1411
|
+
} catch {
|
|
1412
|
+
return url;
|
|
1413
|
+
}
|
|
1414
|
+
})();
|
|
1415
|
+
const hit = matchers.find((m) => m.isMatch(url)) ?? matchers.find((m) => m.isMatch(pathname));
|
|
1416
|
+
if (!hit) {
|
|
1417
|
+
await route.continue();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const spec = hit.response;
|
|
1421
|
+
const isString = typeof spec.body === "string";
|
|
1422
|
+
const isBinary = spec.body instanceof Uint8Array || spec.body instanceof ArrayBuffer;
|
|
1423
|
+
const body = isString ? spec.body : isBinary ? Buffer.from(spec.body) : JSON.stringify(spec.body ?? null);
|
|
1424
|
+
const contentType = spec.contentType ?? (isString ? "text/plain" : "application/json");
|
|
1425
|
+
await route.fulfill({
|
|
1426
|
+
status: spec.status ?? 200,
|
|
1427
|
+
contentType,
|
|
1428
|
+
body,
|
|
1429
|
+
headers: spec.headers
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1397
1433
|
async function takeScreenshot(page, engine, fullPage, errors, cleanup) {
|
|
1398
1434
|
const screenshot = await page.screenshot({ type: "png", fullPage });
|
|
1399
1435
|
const metrics = await page.evaluate(() => ({
|
|
@@ -1578,6 +1614,7 @@ var RenderUseCase = class {
|
|
|
1578
1614
|
const page = await this.pool.getPage(engine);
|
|
1579
1615
|
await page.setViewportSize(options.viewport);
|
|
1580
1616
|
const { errors, cleanup } = attachConsoleCapture(page);
|
|
1617
|
+
if (options.mock) await attachMocks(page, options.mock);
|
|
1581
1618
|
await page.setContent(html, { waitUntil: "load", timeout: 1e4 });
|
|
1582
1619
|
if (options.waitFor > 0) await page.waitForTimeout(options.waitFor);
|
|
1583
1620
|
const result = await takeScreenshot(
|
|
@@ -1593,6 +1630,7 @@ var RenderUseCase = class {
|
|
|
1593
1630
|
async renderUrl(engine, url, options) {
|
|
1594
1631
|
const page = await this.pool.getPage(engine);
|
|
1595
1632
|
const { errors, cleanup } = attachConsoleCapture(page);
|
|
1633
|
+
if (options.mock) await attachMocks(page, options.mock);
|
|
1596
1634
|
const viewport = options.autoFit ? await this.resolveAutoFitViewport(page, url, options) : options.viewport;
|
|
1597
1635
|
await page.setViewportSize(viewport);
|
|
1598
1636
|
await this.navigateAndWait(page, url, options.waitFor);
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createContainer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6OTGZDUU.js";
|
|
5
5
|
import {
|
|
6
6
|
EXT_TO_FRAMEWORK
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WACVDBUF.js";
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { mkdirSync } from "fs";
|
|
11
11
|
import { resolve as resolve2 } from "path";
|
|
12
|
+
import mri from "mri";
|
|
12
13
|
|
|
13
14
|
// src/cli/commands.ts
|
|
14
15
|
import { execSync } from "child_process";
|
|
@@ -16,6 +17,7 @@ import { readFileSync, writeFileSync } from "fs";
|
|
|
16
17
|
import { basename, extname, relative, resolve } from "path";
|
|
17
18
|
|
|
18
19
|
// src/cli/display.ts
|
|
20
|
+
import { createSpinner } from "nanospinner";
|
|
19
21
|
var c = {
|
|
20
22
|
reset: "\x1B[0m",
|
|
21
23
|
bold: "\x1B[1m",
|
|
@@ -50,18 +52,13 @@ function info(label, value) {
|
|
|
50
52
|
log(` ${c.gray}${label.padEnd(6)}${c.reset} ${value}`);
|
|
51
53
|
}
|
|
52
54
|
function spinner(text) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
58
|
-
i++;
|
|
59
|
-
}, 80);
|
|
55
|
+
const s = createSpinner(`${c.dim}${text}${c.reset}`, {
|
|
56
|
+
color: "magenta",
|
|
57
|
+
frames: FRAMES
|
|
58
|
+
}).start();
|
|
60
59
|
return {
|
|
61
60
|
stop(finalText) {
|
|
62
|
-
|
|
63
|
-
process.stderr.write(`\r ${c.green}\u2713${c.reset} ${finalText}\x1B[K
|
|
64
|
-
`);
|
|
61
|
+
s.success({ text: finalText });
|
|
65
62
|
}
|
|
66
63
|
};
|
|
67
64
|
}
|
|
@@ -300,39 +297,27 @@ async function cmdDiff(useCase, filePath, flags, outDir) {
|
|
|
300
297
|
|
|
301
298
|
// src/cli.ts
|
|
302
299
|
function parseArgs(args) {
|
|
303
|
-
const
|
|
304
|
-
|
|
300
|
+
const parsed = mri(args, {
|
|
301
|
+
alias: { o: "out", r: "recursive", h: "help" },
|
|
302
|
+
boolean: ["recursive", "help"],
|
|
303
|
+
string: ["out", "props"],
|
|
304
|
+
default: { out: ".frameshot", recursive: false }
|
|
305
|
+
});
|
|
306
|
+
const [command = "help", positional = ""] = parsed._;
|
|
305
307
|
const flags = {
|
|
306
|
-
out:
|
|
307
|
-
recursive:
|
|
308
|
+
out: parsed.out,
|
|
309
|
+
recursive: parsed.recursive,
|
|
310
|
+
width: parsed.width ? Number(parsed.width) : void 0,
|
|
311
|
+
height: parsed.height ? Number(parsed.height) : void 0
|
|
308
312
|
};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
flags.props = JSON.parse(args[++i]);
|
|
315
|
-
} catch {
|
|
316
|
-
error("Invalid --props JSON");
|
|
317
|
-
}
|
|
318
|
-
break;
|
|
319
|
-
case "--out":
|
|
320
|
-
case "-o":
|
|
321
|
-
flags.out = args[++i];
|
|
322
|
-
break;
|
|
323
|
-
case "--recursive":
|
|
324
|
-
case "-r":
|
|
325
|
-
flags.recursive = true;
|
|
326
|
-
break;
|
|
327
|
-
case "--width":
|
|
328
|
-
flags.width = Number.parseInt(args[++i], 10);
|
|
329
|
-
break;
|
|
330
|
-
case "--height":
|
|
331
|
-
flags.height = Number.parseInt(args[++i], 10);
|
|
332
|
-
break;
|
|
313
|
+
if (parsed.props) {
|
|
314
|
+
try {
|
|
315
|
+
flags.props = JSON.parse(parsed.props);
|
|
316
|
+
} catch {
|
|
317
|
+
error("Invalid --props JSON");
|
|
333
318
|
}
|
|
334
319
|
}
|
|
335
|
-
return { command, positional, flags };
|
|
320
|
+
return { command: String(command), positional: String(positional), flags };
|
|
336
321
|
}
|
|
337
322
|
var HELP = `
|
|
338
323
|
${brand()} ${c.dim}v0.7.0${c.reset}
|
|
@@ -362,7 +347,7 @@ var HELP = `
|
|
|
362
347
|
`;
|
|
363
348
|
async function main() {
|
|
364
349
|
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
365
|
-
if (command === "help"
|
|
350
|
+
if (command === "help") {
|
|
366
351
|
process.stdout.write(HELP);
|
|
367
352
|
return;
|
|
368
353
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createContainer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6OTGZDUU.js";
|
|
5
5
|
import {
|
|
6
6
|
DEVICE_PRESETS,
|
|
7
7
|
EXT_TO_FRAMEWORK,
|
|
8
8
|
__export
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WACVDBUF.js";
|
|
10
10
|
|
|
11
11
|
// src/index.ts
|
|
12
12
|
import { execSync } from "child_process";
|
|
@@ -14528,6 +14528,9 @@ function date4(params) {
|
|
|
14528
14528
|
config(en_default());
|
|
14529
14529
|
|
|
14530
14530
|
// src/tools/tool-utils.ts
|
|
14531
|
+
var mockSchema = external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe(
|
|
14532
|
+
"Mock network responses. Keys: URL pattern (path '/api/users', glob '**/api/*', or full URL). Values: response body (auto-serialized as JSON) or { status, contentType, body, headers }."
|
|
14533
|
+
);
|
|
14531
14534
|
function wrapHandler(handler) {
|
|
14532
14535
|
return async (args) => {
|
|
14533
14536
|
try {
|
|
@@ -14889,7 +14892,8 @@ function registerRenderFileTools(server2, useCase) {
|
|
|
14889
14892
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)"),
|
|
14890
14893
|
projectRoot: external_exports.string().optional().describe(
|
|
14891
14894
|
"Project root directory override (defaults to auto-detect from file path)"
|
|
14892
|
-
)
|
|
14895
|
+
),
|
|
14896
|
+
mock: mockSchema
|
|
14893
14897
|
},
|
|
14894
14898
|
wrapHandler(
|
|
14895
14899
|
async ({
|
|
@@ -14899,7 +14903,8 @@ function registerRenderFileTools(server2, useCase) {
|
|
|
14899
14903
|
height,
|
|
14900
14904
|
darkMode,
|
|
14901
14905
|
tailwindVersion,
|
|
14902
|
-
projectRoot
|
|
14906
|
+
projectRoot,
|
|
14907
|
+
mock
|
|
14903
14908
|
}) => {
|
|
14904
14909
|
const start = performance.now();
|
|
14905
14910
|
const { results, mode } = await useCase.renderFile(path, {
|
|
@@ -14909,7 +14914,8 @@ function registerRenderFileTools(server2, useCase) {
|
|
|
14909
14914
|
engines: ["chromium"],
|
|
14910
14915
|
darkMode,
|
|
14911
14916
|
tailwindVersion,
|
|
14912
|
-
projectRoot
|
|
14917
|
+
projectRoot,
|
|
14918
|
+
mock
|
|
14913
14919
|
});
|
|
14914
14920
|
const elapsed = Math.round(performance.now() - start);
|
|
14915
14921
|
const ext = extname(path).toLowerCase();
|
|
@@ -14948,7 +14954,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14948
14954
|
'Render both: ["light","dark"] returns 2 screenshots for comparison'
|
|
14949
14955
|
),
|
|
14950
14956
|
css: external_exports.string().optional().describe("Custom CSS to inject (design tokens, variables, etc)"),
|
|
14951
|
-
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
14957
|
+
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)"),
|
|
14958
|
+
mock: mockSchema
|
|
14952
14959
|
},
|
|
14953
14960
|
wrapHandler(
|
|
14954
14961
|
async ({
|
|
@@ -14961,7 +14968,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14961
14968
|
darkMode,
|
|
14962
14969
|
colorSchemes,
|
|
14963
14970
|
css,
|
|
14964
|
-
tailwindVersion
|
|
14971
|
+
tailwindVersion,
|
|
14972
|
+
mock
|
|
14965
14973
|
}) => {
|
|
14966
14974
|
const start = performance.now();
|
|
14967
14975
|
const schemes = colorSchemes ?? (darkMode ? ["dark"] : ["light"]);
|
|
@@ -14973,7 +14981,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14973
14981
|
engines,
|
|
14974
14982
|
darkMode: scheme === "dark",
|
|
14975
14983
|
css,
|
|
14976
|
-
tailwindVersion
|
|
14984
|
+
tailwindVersion,
|
|
14985
|
+
mock
|
|
14977
14986
|
})
|
|
14978
14987
|
)
|
|
14979
14988
|
);
|
package/dist/renderer.d.ts
CHANGED
|
@@ -17,6 +17,19 @@ interface RenderOptions {
|
|
|
17
17
|
tailwindVersion: "3" | "4";
|
|
18
18
|
waitFor: number;
|
|
19
19
|
autoFit: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Mock network requests. Keys are URL patterns (path like `/api/users`,
|
|
22
|
+
* glob like `**\/api/*`, or full URLs); values are the response body
|
|
23
|
+
* (auto-serialized as JSON) or a {@link MockResponse} for full control.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* mock: {
|
|
27
|
+
* '/api/users': [{ id: 1, name: 'Alice' }],
|
|
28
|
+
* '/api/posts/*': { status: 200, body: { data: [] } },
|
|
29
|
+
* 'https://api.example.com/x': 'plain text body',
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
mock?: Record<string, unknown>;
|
|
20
33
|
}
|
|
21
34
|
interface ScreenshotResult {
|
|
22
35
|
engine: Engine;
|
|
@@ -150,6 +163,7 @@ declare class ProjectDetector {
|
|
|
150
163
|
detect(filePath: string): ProjectConfig;
|
|
151
164
|
private findProjectRoot;
|
|
152
165
|
private findViteConfig;
|
|
166
|
+
private readPackageJson;
|
|
153
167
|
private detectFramework;
|
|
154
168
|
private checkViteAvailable;
|
|
155
169
|
private checkIsNextJs;
|
package/dist/renderer.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frameshot-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Zero-config visual testing for AI agents. Render project components with full Vite dependency resolution — no stories, no config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -72,6 +72,11 @@
|
|
|
72
72
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
73
73
|
"axe-core": "^4.12.1",
|
|
74
74
|
"chokidar": "^5.0.0",
|
|
75
|
+
"get-tsconfig": "^4.14.0",
|
|
76
|
+
"mri": "^1.2.0",
|
|
77
|
+
"nanospinner": "^1.2.2",
|
|
78
|
+
"picocolors": "^1.1.1",
|
|
79
|
+
"picomatch": "^4.0.4",
|
|
75
80
|
"pixelmatch": "^7.2.0",
|
|
76
81
|
"playwright": "^1.52.0",
|
|
77
82
|
"pngjs": "^7.0.0",
|
|
@@ -88,6 +93,7 @@
|
|
|
88
93
|
"devDependencies": {
|
|
89
94
|
"@biomejs/biome": "^2.5.0",
|
|
90
95
|
"@types/node": "^26.0.0",
|
|
96
|
+
"@types/picomatch": "^4.0.3",
|
|
91
97
|
"@types/pngjs": "^6.0.5",
|
|
92
98
|
"tsup": "^8.0.0",
|
|
93
99
|
"typescript": "^5.7.0",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { execSync } from "node:child_process";
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, extname, join, relative } from "node:path";
|
|
6
|
+
import picomatch from "picomatch";
|
|
6
7
|
|
|
7
8
|
const {
|
|
8
9
|
INPUT_PATHS = "",
|
|
@@ -26,14 +27,13 @@ const COMPONENT_EXTS = new Set(
|
|
|
26
27
|
INPUT_EXTENSIONS.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean),
|
|
27
28
|
);
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// Compile exclude globs once via picomatch — handles `*`, `**`, `?`, `[abc]`,
|
|
31
|
+
// brace expansion, and negation properly. Match against the file basename.
|
|
32
|
+
const excludeGlobs = INPUT_EXCLUDE.split(",")
|
|
30
33
|
.map((p) => p.trim())
|
|
31
|
-
.filter(Boolean)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const escaped = p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
|
|
35
|
-
return new RegExp(escaped);
|
|
36
|
-
});
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
const isExcludedGlob =
|
|
36
|
+
excludeGlobs.length > 0 ? picomatch(excludeGlobs) : () => false;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Derive a human-friendly display label from a file path.
|
|
@@ -80,8 +80,7 @@ function safeFileId(label) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function isExcluded(filePath) {
|
|
83
|
-
|
|
84
|
-
return EXCLUDE_PATTERNS.some((re) => re.test(base));
|
|
83
|
+
return isExcludedGlob(basename(filePath));
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
function findFiles(workspace, patterns) {
|