Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions packages/core/src/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,31 @@ export function getPageFiles(path: string, options: ResolvedOptions): string[] {

const ext = extsToGlob(FILE_EXTENSIONS)

const files = fg.sync(`**/*.${ext}`, {
ignore: exclude,
// 分离排除规则中的否定模式(以!开头的规则)
const positivePatterns = exclude.filter(pattern => !pattern.startsWith('!'))
const negativePatterns = exclude.filter(pattern => pattern.startsWith('!'))
.map(pattern => pattern.slice(1)) // 移除!前缀

// 先获取所有匹配的文件
let files = fg.sync(`**/*.${ext}`, {
ignore: positivePatterns, // 只使用肯定模式进行初始过滤
onlyFiles: true,
cwd: path,
})

// 如果有否定模式(即应该包含的模式),需要特殊处理
if (negativePatterns.length > 0) {
// 获取所有应该包含的文件
const includedFiles = fg.sync(negativePatterns.map(pattern => `${pattern}`), {
onlyFiles: true,
cwd: path,
})

// 将应该包含的文件添加回结果集,确保不重复
const filesSet = new Set([...files, ...includedFiles])
files = Array.from(filesSet)
}
Comment on lines +31 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: re-included files are not filtered by allowed extensions.

negativePatterns are globbed without extension filtering, so non-page files (e.g., images, JSON) can leak into results. Filter by FILE_EXTENSIONS before merging and ensure deterministic ordering.

Apply this patch:

-    const includedFiles = fg.sync(negativePatterns.map(pattern => `${pattern}`), {
-      onlyFiles: true,
-      cwd: path,
-    })
-
-    // 将应该包含的文件添加回结果集,确保不重复
-    const filesSet = new Set([...files, ...includedFiles])
-    files = Array.from(filesSet)
+    const includedFiles = fg.sync(negativePatterns, {
+      onlyFiles: true,
+      cwd: path,
+    })
+    // 仅保留受支持的页面后缀
+    const includedPageFiles = includedFiles.filter(f =>
+      FILE_EXTENSIONS.some(ext => f.endsWith(`.${ext}`)),
+    )
+    // 合并并保持结果稳定(去重 + 排序)
+    const filesSet = new Set([...files, ...includedPageFiles])
+    files = Array.from(filesSet).sort()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/core/src/files.ts around lines 31 to 39, the code re-includes files
matched by negativePatterns without filtering by allowed FILE_EXTENSIONS and
without deterministic ordering; update the logic so includedFiles are filtered
to only keep files whose extensions are in the FILE_EXTENSIONS list (use the
file extension check, e.g., via path.extname and normalizing the extension)
before merging into filesSet, then produce the final files array by converting
filesSet to an array and sorting it (deterministic ordering) so duplicates are
removed, only allowed extensions are included, and ordering is stable.


return files
}

Expand Down
5 changes: 5 additions & 0 deletions packages/playground/src/pages-sub/about/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/src/pages-sub/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/src/pages-sub2/about/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
7 changes: 7 additions & 0 deletions packages/playground/src/pages-sub2/about/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script></script>

<template>
<div>
my
</div>
</template>
7 changes: 7 additions & 0 deletions packages/playground/src/pages-sub2/about/your.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script></script>

<template>
<div>
your
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/src/pages-sub2/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
60 changes: 60 additions & 0 deletions packages/playground/src/pages-sub2/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
import { ref } from 'vue'

const title = ref('Hello')
function nav() {
uni.navigateTo({
url: '/pages/about/index',
})
}
</script>

<template>
<view class="content">
<image class="logo" src="/static/logo.png" />
<view class="text-area">
<text class="title">
{{ title }}
</text>
</view>
<button @click="nav">
nav
</button>
</view>
</template>

<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}

.text-area {
display: flex;
justify-content: center;
}

.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

<route lang="jsonc">
{
"style": {
"navigationBarTitleText": "test json sub page"
}
}
</route>
5 changes: 5 additions & 0 deletions packages/playground/src/pages/blog/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/src/pages/components/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
comp
</div>
</template>
5 changes: 5 additions & 0 deletions packages/playground/src/pages/components/dir/nest-comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
nest-comp
</div>
</template>
4 changes: 4 additions & 0 deletions packages/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default defineConfig({
homePage: 'pages/index',
debug: true,
subPackages: ['src/pages-sub', 'src/pages-sub2'],
exclude: [
'pages*/**/components/**/*.*', // 排除所有pages 里面的 components 文件夹
'!pages-sub2/**/components/**/*.*', // 不排除 pages-sub2/**/components
],
// configSource: [
// {
// files: 'vite.config',
Expand Down
169 changes: 146 additions & 23 deletions test/files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,156 @@ import process from 'node:process'
import { describe, expect, it } from 'vitest'
import { getPageFiles, resolveOptions } from '../packages/core/src'

const options = resolveOptions({}, process.cwd())
const pages = 'packages/playground/src/pages'
const pages = 'packages/playground/src'

describe('get files', () => {
const options = resolveOptions({}, process.cwd())
describe('get all files', () => {
it('pages', async () => {
const files = getPageFiles(pages, options)
expect(files.sort()).toMatchInlineSnapshot(`
[
"A-top.vue",
"blog/index.vue",
"blog/post.vue",
"define-page/async-function.vue",
"define-page/conditional-compilation.vue",
"define-page/function.vue",
"define-page/nested-function.vue",
"define-page/object.vue",
"define-page/option-api.vue",
"define-page/remove-console.vue",
"define-page/yaml.vue",
"i18n.vue",
"index.nvue",
"index.vue",
"test-json.vue",
"test-jsonc-with-comment.vue",
"test-yaml.vue",
"test.vue",
]
[
"App.vue",
"components/Counter.vue",
"pages-sub-pages/sub-activity/pages/about/index.vue",
"pages-sub-pages/sub-activity/pages/home/index.vue",
"pages-sub-pages/sub-main/pages/about/index.nvue",
"pages-sub-pages/sub-main/pages/about/index.vue",
"pages-sub-pages/sub-main/pages/home/index.vue",
"pages-sub/about/components/comp.vue",
"pages-sub/about/components/dir/nest-comp.vue",
"pages-sub/about/index.vue",
"pages-sub/about/your.vue",
"pages-sub/components/comp.vue",
"pages-sub/components/dir/nest-comp.vue",
"pages-sub/index.vue",
"pages-sub2/about/components/comp.vue",
"pages-sub2/about/components/dir/nest-comp.vue",
"pages-sub2/about/index.vue",
"pages-sub2/about/your.vue",
"pages-sub2/components/comp.vue",
"pages-sub2/components/dir/nest-comp.vue",
"pages-sub2/index.vue",
"pages/A-top.vue",
"pages/blog/components/comp.vue",
"pages/blog/components/dir/nest-comp.vue",
"pages/blog/index.vue",
"pages/blog/post.vue",
"pages/components/comp.vue",
"pages/components/dir/nest-comp.vue",
"pages/define-page/async-function.vue",
"pages/define-page/conditional-compilation.vue",
"pages/define-page/function.vue",
"pages/define-page/nested-function.vue",
"pages/define-page/object.vue",
"pages/define-page/option-api.vue",
"pages/define-page/remove-console.vue",
"pages/define-page/yaml.vue",
"pages/i18n.vue",
"pages/index.nvue",
"pages/index.vue",
"pages/test-json.vue",
"pages/test-jsonc-with-comment.vue",
"pages/test-yaml.vue",
"pages/test.vue",
]
`)
})
})

const options2 = resolveOptions({
exclude: ['pages*/**/components/**/*.*'],
}, process.cwd())
describe('get all files except components', () => {
it('pages2', async () => {
const files = getPageFiles(pages, options2)
expect(files.sort()).toMatchInlineSnapshot(`
[
"App.vue",
"components/Counter.vue",
"pages-sub-pages/sub-activity/pages/about/index.vue",
"pages-sub-pages/sub-activity/pages/home/index.vue",
"pages-sub-pages/sub-main/pages/about/index.nvue",
"pages-sub-pages/sub-main/pages/about/index.vue",
"pages-sub-pages/sub-main/pages/home/index.vue",
"pages-sub/about/index.vue",
"pages-sub/about/your.vue",
"pages-sub/index.vue",
"pages-sub2/about/index.vue",
"pages-sub2/about/your.vue",
"pages-sub2/index.vue",
"pages/A-top.vue",
"pages/blog/index.vue",
"pages/blog/post.vue",
"pages/define-page/async-function.vue",
"pages/define-page/conditional-compilation.vue",
"pages/define-page/function.vue",
"pages/define-page/nested-function.vue",
"pages/define-page/object.vue",
"pages/define-page/option-api.vue",
"pages/define-page/remove-console.vue",
"pages/define-page/yaml.vue",
"pages/i18n.vue",
"pages/index.nvue",
"pages/index.vue",
"pages/test-json.vue",
"pages/test-jsonc-with-comment.vue",
"pages/test-yaml.vue",
"pages/test.vue",
]
`)
})
})

const options3 = resolveOptions({
exclude: [
// 排除所有pages 里面的 components 文件夹
'pages*/**/components/**/*.*',
// 不排除 pages-sub2/**/components
'!pages-sub2/**/components/**/*.*',
],
}, process.cwd())

describe('get all files except components but not pages-sub2/**/components', () => {
it('pages3', async () => {
const files = getPageFiles(pages, options3)
expect(files.sort()).toMatchInlineSnapshot(`
[
"App.vue",
"components/Counter.vue",
"pages-sub-pages/sub-activity/pages/about/index.vue",
"pages-sub-pages/sub-activity/pages/home/index.vue",
"pages-sub-pages/sub-main/pages/about/index.nvue",
"pages-sub-pages/sub-main/pages/about/index.vue",
"pages-sub-pages/sub-main/pages/home/index.vue",
"pages-sub/about/index.vue",
"pages-sub/about/your.vue",
"pages-sub/index.vue",
"pages-sub2/about/components/comp.vue",
"pages-sub2/about/components/dir/nest-comp.vue",
"pages-sub2/about/index.vue",
"pages-sub2/about/your.vue",
"pages-sub2/components/comp.vue",
"pages-sub2/components/dir/nest-comp.vue",
"pages-sub2/index.vue",
"pages/A-top.vue",
"pages/blog/index.vue",
"pages/blog/post.vue",
"pages/define-page/async-function.vue",
"pages/define-page/conditional-compilation.vue",
"pages/define-page/function.vue",
"pages/define-page/nested-function.vue",
"pages/define-page/object.vue",
"pages/define-page/option-api.vue",
"pages/define-page/remove-console.vue",
"pages/define-page/yaml.vue",
"pages/i18n.vue",
"pages/index.nvue",
"pages/index.vue",
"pages/test-json.vue",
"pages/test-jsonc-with-comment.vue",
"pages/test-yaml.vue",
"pages/test.vue",
]
`)
})
})