
























@@ -702,6 +702,96 @@ export function filterBootstrapFilesForSession(
702702return files.filter((file) => MINIMAL_BOOTSTRAP_ALLOWLIST.has(file.name));
703703}
704704705+function hasGlobPattern(pattern: string): boolean {
706+// Keep square brackets literal here; workspace paths commonly contain them.
707+return /[?*{}]/u.test(pattern);
708+}
709+710+function normalizeWorkspacePatternPath(value: string): string {
711+return value
712+.replaceAll(path.sep, "/")
713+.replaceAll("\\", "/")
714+.replace(/^\.\/+/u, "");
715+}
716+717+function resolveGlobWalkRoot(pattern: string): string {
718+const normalized = normalizeWorkspacePatternPath(pattern);
719+const globIndex = normalized.search(/[?*{}]/u);
720+if (globIndex === -1) {
721+return normalized;
722+}
723+const slashIndex = normalized.lastIndexOf("/", globIndex);
724+return slashIndex === -1 ? "." : normalized.slice(0, slashIndex) || ".";
725+}
726+727+async function* walkWorkspaceFiles(
728+workspaceDir: string,
729+initialRelativeDir: string,
730+): AsyncGenerator<string> {
731+const stack = [initialRelativeDir === "." ? "" : initialRelativeDir];
732+while (stack.length > 0) {
733+const currentRelativeDir = stack.pop() ?? "";
734+const currentDir = path.resolve(workspaceDir, currentRelativeDir);
735+const relativeToWorkspace = path.relative(workspaceDir, currentDir);
736+if (relativeToWorkspace.startsWith("..") || path.isAbsolute(relativeToWorkspace)) {
737+continue;
738+}
739+740+let entries: syncFs.Dirent[];
741+try {
742+entries = await fs.readdir(currentDir, { withFileTypes: true });
743+} catch {
744+continue;
745+}
746+747+for (const entry of entries) {
748+const childRelativePath = currentRelativeDir
749+ ? path.join(currentRelativeDir, entry.name)
750+ : entry.name;
751+if (entry.isDirectory()) {
752+stack.push(childRelativePath);
753+continue;
754+}
755+if (entry.isFile() || entry.isSymbolicLink()) {
756+yield normalizeWorkspacePatternPath(childRelativePath);
757+}
758+}
759+}
760+}
761+762+async function resolveExtraBootstrapPatternPaths(
763+workspaceDir: string,
764+pattern: string,
765+): Promise<string[]> {
766+if (typeof fs.glob === "function") {
767+try {
768+const matches: string[] = [];
769+for await (const match of fs.glob(pattern, { cwd: workspaceDir })) {
770+matches.push(match);
771+}
772+return matches;
773+} catch {
774+// Fall through to the local matcher before treating the pattern as literal.
775+}
776+}
777+778+if (typeof path.matchesGlob !== "function") {
779+return [pattern];
780+}
781+782+const normalizedPattern = normalizeWorkspacePatternPath(pattern);
783+const matches: string[] = [];
784+for await (const candidate of walkWorkspaceFiles(
785+workspaceDir,
786+resolveGlobWalkRoot(normalizedPattern),
787+)) {
788+if (path.matchesGlob(candidate, normalizedPattern)) {
789+matches.push(candidate);
790+}
791+}
792+return matches.length > 0 ? matches : [pattern];
793+}
794+705795export async function loadExtraBootstrapFiles(
706796dir: string,
707797extraPatterns: string[],
@@ -725,15 +815,10 @@ export async function loadExtraBootstrapFilesWithDiagnostics(
725815// Resolve glob patterns into concrete file paths
726816const resolvedPaths = new Set<string>();
727817for (const pattern of extraPatterns) {
728-if (pattern.includes("*") || pattern.includes("?") || pattern.includes("{")) {
729-try {
730-const matches = fs.glob(pattern, { cwd: resolvedDir });
731-for await (const m of matches) {
732-resolvedPaths.add(m);
733-}
734-} catch {
735-// glob not available or pattern error — fall back to literal
736-resolvedPaths.add(pattern);
818+if (hasGlobPattern(pattern)) {
819+const matches = await resolveExtraBootstrapPatternPaths(resolvedDir, pattern);
820+for (const match of matches) {
821+resolvedPaths.add(match);
737822}
738823} else {
739824resolvedPaths.add(pattern);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。