diff --git a/.changeset/e2e-watch-group-entry.md b/.changeset/e2e-watch-group-entry.md new file mode 100644 index 000000000..df7370b1f --- /dev/null +++ b/.changeset/e2e-watch-group-entry.md @@ -0,0 +1,9 @@ +--- +"weapp-tailwindcss": patch +--- + +完善 `e2e:watch` 热更新回归流程: + +- 新增 `demo` 与 `apps` 分组测试入口,避免分组执行时重复跑单 case 文件 +- 将 `test:watch-hmr` 切换为 `node --import tsx` 启动,修复部分环境下 `tsx` IPC `EPERM` 导致的回归无法启动问题 +- 调整 `apps/taro-webpack-tailwindcss-v4` 的 watch 回归命令,确保 Taro webpack 场景下模板、脚本、样式热更新都能稳定校验 diff --git a/.changeset/honest-hounds-hug.md b/.changeset/honest-hounds-hug.md new file mode 100644 index 000000000..1ccf911e7 --- /dev/null +++ b/.changeset/honest-hounds-hug.md @@ -0,0 +1,7 @@ +--- +"weapp-tailwindcss": patch +--- + +增强多平台热更新回归覆盖,补齐 `uni-app`、`uni-app-vue3-vite`、`mpx` 的 comment-carrier 场景,并新增汇总断言校验 same-class 稳定性、comment-carrier 命中数量与热更新时间指标。 + +修复 `uni-app-vue3-vite` 在 comment-carrier 场景下 marker 无法进入运行时输出导致 watch-hmr 卡住的问题,同时将关键 HMR 用例接入 `E2E Watch` 工作流,确保 PR 与夜间任务都能持续校验多平台热更新链路。 diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..bf4e85288 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,80 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "react-app": "0.0.0", + "rsmax-app-ts": "1.0.0", + "tailwindcss-weapp": "0.0.1", + "taro-webpack-tailwindcss-v4": "1.0.0", + "uni-app-x-hbuilderx-tailwindcss3": "0.0.0", + "uni-app-x-hbuilderx-tailwindcss4": "0.0.0", + "vite-native": "1.0.19", + "vite-native-skyline": "1.0.1", + "vite-native-ts": "1.0.12", + "vite-native-ts-skyline": "1.0.1", + "vue-app": "0.0.0", + "weapp-wechat-zhihu": "1.0.0", + "@native-app/postcss7-compat": "1.0.1", + "benchmark": "0.0.1", + "benchmark-tailwindcss3": "0.0.8", + "benchmark-tailwindcss4": "0.0.6", + "@weapp-tailwindcss-demo/gulp-app": "0.0.5", + "@weapp-tailwindcss-demo/mpx-app": "0.1.0", + "@weapp-tailwindcss-demo/mpx-tailwindcss-v4": "0.1.0", + "@weapp-tailwindcss-demo/native": "1.0.0", + "@weapp-tailwindcss-demo/native-mina": "1.0.0", + "@weapp-tailwindcss-demo/native-ts": "1.0.0", + "@weapp-tailwindcss-demo/rax-app": "0.1.0", + "@weapp-tailwindcss-demo/taro-app": "1.0.1", + "@weapp-tailwindcss-demo/taro-app-vite": "1.0.0", + "@weapp-tailwindcss-demo/taro-vite-tailwindcss-v4": "1.0.0", + "@weapp-tailwindcss-demo/taro-vue3-app": "1.0.0", + "@weapp-tailwindcss-demo/taro-webpack-tailwindcss-v4": "1.0.0", + "@weapp-tailwindcss-demo/uni-app": "0.1.0", + "@weapp-tailwindcss-demo/uni-app-tailwindcss-v4": "0.0.0", + "@weapp-tailwindcss-demo/uni-app-vue3-vite": "0.0.1", + "@weapp-tailwindcss-demo/uni-app-webpack-tailwindcss-v4": "0.1.0", + "@weapp-tailwindcss-demo/uni-app-webpack5": "0.1.0", + "@weapp-tailwindcss-demo/uni-app-x-hbuilderx-tailwindcss3": "0.0.0", + "@weapp-tailwindcss-demo/uni-app-x-hbuilderx-tailwindcss4": "0.0.0", + "@weapp-tailwindcss-demo/web": "1.0.0", + "@weapp-tailwindcss/e2e": "0.0.0", + "@weapp-tailwindcss/cva": "0.1.6", + "@weapp-tailwindcss/merge": "2.1.6", + "@weapp-tailwindcss/merge-v3": "0.1.6", + "@weapp-tailwindcss/runtime": "0.1.5", + "tailwind-variant-v3": "0.2.1", + "theme-transition": "2.0.1", + "@weapp-tailwindcss/typography": "0.2.6", + "@weapp-tailwindcss/ui": "0.0.6", + "@weapp-tailwindcss/variants": "0.2.1", + "@weapp-tailwindcss/variants-v3": "0.1.1", + "@weapp-tailwindcss/babel": "0.0.3", + "@weapp-tailwindcss/build-all": "0.0.21", + "@weapp-tailwindcss/debug-uni-app-x": "0.0.3", + "@weapp-tailwindcss/experimental": "0.0.1", + "@weapp-tailwindcss/init": "1.0.10", + "@weapp-tailwindcss/logger": "1.1.0", + "@weapp-tailwindcss/minify-preserve": "0.0.0", + "@weapp-tailwindcss/postcss": "2.1.5", + "@weapp-tailwindcss/shared": "1.1.2", + "tailwindcss-config": "1.1.4", + "tailwindcss-core-plugins-extractor": "0.2.0", + "tailwindcss-injector": "1.0.10", + "@weapp-tailwindcss/test-helper": "0.0.1", + "weapp-style-injector": "0.0.1", + "weapp-tailwindcss": "4.10.3", + "weapp-tw": "0.0.0", + "weapptw": "0.0.0", + "wetw": "0.1.1", + "@weapp-tailwindcss/website": "1.0.17" + }, + "changesets": [ + "bright-horses-clean", + "e2e-watch-group-entry", + "honest-hounds-hug", + "perf-hot-path-caching", + "tailwindcss-patch-874-alpha", + "weapp-tailwindcss-alias" + ] +} diff --git a/.changeset/tailwindcss-patch-874-alpha.md b/.changeset/tailwindcss-patch-874-alpha.md new file mode 100644 index 000000000..12a845bd0 --- /dev/null +++ b/.changeset/tailwindcss-patch-874-alpha.md @@ -0,0 +1,13 @@ +--- +'weapp-tailwindcss': patch +'weapp-tw': patch +'tailwindcss-injector': patch +'@weapp-tailwindcss/postcss': patch +'@weapp-tailwindcss/ui': patch +'@weapp-tailwindcss/shared': patch +'@weapp-tailwindcss/init': patch +'@weapp-tailwindcss/typography': patch +'wetw': patch +--- + +升级 `tailwindcss-patch` 到 `8.7.4-alpha.0`,同步消费最新的 alpha 版本依赖。 diff --git a/.changeset/weapp-tailwindcss-alias.md b/.changeset/weapp-tailwindcss-alias.md new file mode 100644 index 000000000..78662ab55 --- /dev/null +++ b/.changeset/weapp-tailwindcss-alias.md @@ -0,0 +1,10 @@ +--- +'weapp-tailwindcss': minor +--- + +为所有编译插件入口新增 `weappTailwindcss` 别名导出,方便用户统一简写引用: + +- `weapp-tailwindcss/webpack` → `UnifiedWebpackPluginV5` 的别名 +- `weapp-tailwindcss/webpack4` → `UnifiedWebpackPluginV4` 的别名 +- `weapp-tailwindcss/vite` → `UnifiedViteWeappTailwindcssPlugin` 的别名 +- `weapp-tailwindcss/gulp` → `createPlugins` 的别名 diff --git a/.claude/skills/playwright-cli/references/test-generation.md b/.claude/skills/playwright-cli/references/test-generation.md index 0cfd77024..222f94139 100644 --- a/.claude/skills/playwright-cli/references/test-generation.md +++ b/.claude/skills/playwright-cli/references/test-generation.md @@ -45,8 +45,8 @@ test('login flow', async ({ page }) => { await page.getByRole('textbox', { name: 'Password' }).fill('password123') await page.getByRole('button', { name: 'Sign In' }).click() - // Add assertions - await expect(page).toHaveURL(/.*dashboard/) + // 验证跳转到 dashboard + await expect(page).toHaveURL('https://example.com/dashboard') }) ``` diff --git a/.codex/skills/playwright-cli/references/test-generation.md b/.codex/skills/playwright-cli/references/test-generation.md index 0cfd77024..222f94139 100644 --- a/.codex/skills/playwright-cli/references/test-generation.md +++ b/.codex/skills/playwright-cli/references/test-generation.md @@ -45,8 +45,8 @@ test('login flow', async ({ page }) => { await page.getByRole('textbox', { name: 'Password' }).fill('password123') await page.getByRole('button', { name: 'Sign In' }).click() - // Add assertions - await expect(page).toHaveURL(/.*dashboard/) + // 验证跳转到 dashboard + await expect(page).toHaveURL('https://example.com/dashboard') }) ``` diff --git a/.gitattributes b/.gitattributes index f3baa5988..536b92ccc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,7 +14,8 @@ ## Handle line endings automatically for files detected as ## text and leave all files detected as binary untouched. ## This will handle all files NOT defined below. -* text=auto +## 强制所有文本文件使用 LF,避免 Windows CI 上 CRLF 导致 prettier 报错 +* text=auto eol=lf # Source code *.bash text eol=lf diff --git a/.github/scripts/e2e-watch-report.cjs b/.github/scripts/e2e-watch-report.cjs index 296d324be..7db658260 100644 --- a/.github/scripts/e2e-watch-report.cjs +++ b/.github/scripts/e2e-watch-report.cjs @@ -6,6 +6,37 @@ const fs = require('node:fs') const path = require('node:path') const process = require('node:process') +/** 匹配换行符(兼容 CRLF) */ +const CRLF_RE = /\r?\n/ +/** 替换换行为转义表示 */ +const NEWLINE_REPLACE_RE = /\n/g +/** 匹配 rollback 阶段(不区分大小写) */ +const ROLLBACK_RE = /rollback/i +/** 按管道符分隔 token */ +const TOKEN_SEPARATOR_RE = /\s+\|\s+/ +/** 匹配截断的 bg hex / 未闭合 bg / 未闭合 px token(多行) */ +const BG_PX_FALLBACK_RE = /\bbg-\s+\[#?[0-9a-fA-F]{3,8}\]?|\bbg-\[[^\]]*$|\bpx-\[[^\]]*$/m +/** 截断的 bg hex token */ +const TRUNCATED_BG_HEX_RE = /\bbg-\s+\[#?[0-9a-fA-F]{3,8}\]?/g +/** 未闭合 bg token */ +const UNTERMINATED_BG_RE = /\bbg-\[[^\]]*$/gm +/** 未闭合 px token */ +const UNTERMINATED_PX_RE = /\bpx-\[[^\]]*$/gm +/** bg token 内含空白 */ +const BG_WHITESPACE_INSIDE_RE = /\bbg-\[[^\]\s]*\s[^\]\s]*\]/g +/** px token 内含空白 */ +const PX_WHITESPACE_INSIDE_RE = /\bpx-\[[^\]\s]*\s[^\]\s]*\]/g +/** 缓存失效相关关键词 */ +const CACHE_INVALIDATION_RE = /invalidation|context-not-found|cache/ +/** 文件系统竞态相关关键词 */ +const FS_RACE_RE = /enoent|eperm|ebusy|eacces|crlf|lf|rename|path/ +/** 进程/超时相关关键词 */ +const PROCESS_TIMEOUT_RE = /timeout|exceeded|watch process exited|sigkill|fatal|killed/ +/** 匹配 mutation kind */ +const MUTATION_KIND_RE = /mutation=(template|script|style)/g +/** 匹配 round 名称 */ +const ROUND_NAME_RE = /round=([a-z0-9-]+)/g + const ROOT_DIR = path.resolve(process.cwd(), 'e2e/benchmark/e2e-watch-hmr') const SNAPSHOTS_DIR = path.join(ROOT_DIR, 'snapshots') const FAILURES_DIR = path.join(ROOT_DIR, 'failures') @@ -35,7 +66,7 @@ function listFilesSafe(dir, filter) { function parseKvContent(content) { const out = {} - for (const line of content.split(/\r?\n/)) { + for (const line of content.split(CRLF_RE)) { const index = line.indexOf('=') if (index <= 0) { continue @@ -61,10 +92,10 @@ function summarizeDiff(before, after) { } const beforeContext = before .slice(Math.max(0, index - 40), Math.min(before.length, index + 120)) - .replace(/\n/g, '\\n') + .replace(NEWLINE_REPLACE_RE, '\\n') const afterContext = after .slice(Math.max(0, index - 40), Math.min(after.length, index + 120)) - .replace(/\n/g, '\\n') + .replace(NEWLINE_REPLACE_RE, '\\n') return `firstDiff=${index}, len=${before.length}->${after.length}\n before=${beforeContext}\n after=${afterContext}` } @@ -99,7 +130,7 @@ function resolvePhase(rawPhase, errorText) { if (rawPhase === 'add' || rawPhase === 'modify') { return 'hot-update' } - if (/rollback/i.test(errorText)) { + if (ROLLBACK_RE.test(errorText)) { return 'rollback' } return 'hot-update' @@ -118,13 +149,13 @@ function pickPrimaryFailure(failureLogs, failureSnapshots) { phase: resolvePhase(kv.phase || '', kv.error || ''), project: kv.project || 'unknown', sourceFile: kv.source || 'unknown', - tokens: (kv.tokens || '').split(/\s+\|\s+/).filter(Boolean), + tokens: (kv.tokens || '').split(TOKEN_SEPARATOR_RE).filter(Boolean), error: kv.error || '', } } if (failureSnapshots.length > 0) { - const item = failureSnapshots[failureSnapshots.length - 1] + const item = failureSnapshots.at(-1) return { source: 'snapshot', file: item.dir, @@ -164,7 +195,7 @@ function pickFailureSnapshot(primary, snapshots) { && item.meta.roundName === primary.round && item.meta.phase === primary.phaseRaw, ) - return exact || candidates[candidates.length - 1] + return exact || candidates.at(-1) } function pickMetricFromReport(report, primary) { @@ -218,9 +249,7 @@ function pickSnippet(source, probes) { } if (hitIndex < 0) { - const fallback = source.match( - /\bbg-\s+\[#?[0-9a-fA-F]{3,8}\]?|\bbg-\[[^\]]*$|\bpx-\[[^\]]*$/m, - ) + const fallback = source.match(BG_PX_FALLBACK_RE) if (fallback?.index != null) { hitIndex = fallback.index } @@ -237,11 +266,11 @@ function pickSnippet(source, probes) { function detectTokenAnomalies(source) { const patterns = [ - { name: 'truncated-bg-hex', re: /\bbg-\s+\[#?[0-9a-fA-F]{3,8}\]?/g }, - { name: 'unterminated-bg-token', re: /\bbg-\[[^\]]*$/gm }, - { name: 'unterminated-px-token', re: /\bpx-\[[^\]]*$/gm }, - { name: 'bg-whitespace-inside-token', re: /\bbg-\[[^\]\s]*\s[^\]\s]*\]/g }, - { name: 'px-whitespace-inside-token', re: /\bpx-\[[^\]\s]*\s[^\]\s]*\]/g }, + { name: 'truncated-bg-hex', re: TRUNCATED_BG_HEX_RE }, + { name: 'unterminated-bg-token', re: UNTERMINATED_BG_RE }, + { name: 'unterminated-px-token', re: UNTERMINATED_PX_RE }, + { name: 'bg-whitespace-inside-token', re: BG_WHITESPACE_INSIDE_RE }, + { name: 'px-whitespace-inside-token', re: PX_WHITESPACE_INSIDE_RE }, ] const findings = [] @@ -267,7 +296,7 @@ function scoreAttribution(primary, evidence) { ['进程/超时问题', 0], ]) - if (/invalidation|context-not-found|cache/.test(text)) { + if (CACHE_INVALIDATION_RE.test(text)) { scores.set( 'cache key/invalidation', scores.get('cache key/invalidation') + 2, @@ -286,13 +315,13 @@ function scoreAttribution(primary, evidence) { scores.get('transform emit mismatch') + 3, ) } - if (/enoent|eperm|ebusy|eacces|crlf|lf|rename|path/.test(text)) { + if (FS_RACE_RE.test(text)) { scores.set( '文件系统竞态/路径换行差异', scores.get('文件系统竞态/路径换行差异') + 3, ) } - if (/timeout|exceeded|watch process exited|sigkill|fatal|killed/.test(text)) { + if (PROCESS_TIMEOUT_RE.test(text)) { scores.set('进程/超时问题', scores.get('进程/超时问题') + 3) } if (primary.phase === 'rollback') { @@ -393,7 +422,7 @@ function generateDiffSummary() { lines.push(`- ${path.basename(file)}`) const content = readUtf8(file).trim() if (content) { - for (const line of content.split(/\r?\n/)) { + for (const line of content.split(CRLF_RE)) { lines.push(` ${line}`) } } @@ -619,8 +648,8 @@ function publishJobSummary() { for (const log of logs) { const content = fs.readFileSync(path.join(FAILURES_DIR, log), 'utf8') const kindMatches - = content.match(/mutation=(template|script|style)/g) || [] - const roundMatches = content.match(/round=([a-z0-9-]+)/g) || [] + = content.match(MUTATION_KIND_RE) || [] + const roundMatches = content.match(ROUND_NAME_RE) || [] for (const matched of kindMatches) { failedKinds.add(matched.replace('mutation=', '')) } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13f870199..6dc4da818 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,16 @@ on: # branches: ['main'] pull_request: types: [opened, synchronize] + paths-ignore: + - 'website/**' + - '**/*.md' + - '.changeset/**' workflow_dispatch: +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -17,8 +25,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [20, 22, 24] + include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '[{"os":"ubuntu-latest","node-version":22},{"os":"ubuntu-latest","node-version":20},{"os":"ubuntu-latest","node-version":24},{"os":"windows-latest","node-version":22},{"os":"macos-latest","node-version":22}]' || '[{"os":"ubuntu-latest","node-version":22}]') }} runs-on: ${{ matrix.os }} # Remote Caching enabled - configure TURBO_TOKEN and TURBO_TEAM in repository settings env: @@ -50,9 +57,6 @@ jobs: - name: Lint run: pnpm lint - - name: SEO Quality Gate (website) - run: pnpm --filter @weapp-tailwindcss/website seo:quality:strict - - name: Build run: pnpm build @@ -64,11 +68,13 @@ jobs: run: | { echo "## CI 状态说明" - echo "- 本工作流负责:lint、website SEO 质量门禁、build、unit/integration tests、coverage 上传。" + echo "- 本工作流负责:lint、build、unit/integration tests、coverage 上传。" echo "- \`pnpm e2e:watch\` 已拆分到独立工作流:\`E2E Watch\`(.github/workflows/e2e-watch.yml)。" + echo "- website SEO 质量门禁已拆分到独立工作流:\`Website SEO Quality\`(.github/workflows/website-seo-quality.yml)。" } >> "$GITHUB_STEP_SUMMARY" - name: Upload coverage reports to Codecov + if: matrix.os == 'ubuntu-latest' && matrix.node-version == 22 uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/e2e-watch.yml b/.github/workflows/e2e-watch.yml index d5ae91811..e2d37be7f 100644 --- a/.github/workflows/e2e-watch.yml +++ b/.github/workflows/e2e-watch.yml @@ -8,13 +8,17 @@ on: - 'e2e/watch/**' - 'demo/**' - 'apps/**' + - 'scripts/**' + - '.github/scripts/**' - package.json - pnpm-lock.yaml - pnpm-workspace.yaml - .github/workflows/e2e-watch.yml + paths-ignore: + - 'website/**' + - '**/*.md' + - '.changeset/**' workflow_dispatch: - schedule: - - cron: '0 4 * * *' concurrency: group: e2e-watch-${{ github.event.pull_request.number || github.ref }} @@ -30,61 +34,7 @@ jobs: strategy: fail-fast: false matrix: - include: - - os: ubuntu-latest - runner_label: ubuntu - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 25 - watch_timeout_ms: '180000' - watch_poll_ms: '200' - watch_command_timeout_ms: '420000' - - os: ubuntu-latest - runner_label: ubuntu - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 25 - watch_timeout_ms: '180000' - watch_poll_ms: '200' - watch_command_timeout_ms: '420000' - - os: macos-latest - runner_label: macos - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 35 - watch_timeout_ms: '240000' - watch_poll_ms: '280' - watch_command_timeout_ms: '600000' - - os: macos-latest - runner_label: macos - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 35 - watch_timeout_ms: '240000' - watch_poll_ms: '280' - watch_command_timeout_ms: '600000' - - os: windows-latest - runner_label: windows - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 40 - watch_timeout_ms: '280000' - watch_poll_ms: '320' - watch_command_timeout_ms: '720000' - - os: windows-latest - runner_label: windows - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 40 - watch_timeout_ms: '280000' - watch_poll_ms: '320' - watch_command_timeout_ms: '720000' + include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '[{"os":"macos-latest","runner_label":"macos","watch_case":"uni","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"mpx","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"uni-app-vue3-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"weapp-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"demo","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":65,"watch_timeout_ms":"420000","watch_poll_ms":"300","watch_command_timeout_ms":"1200000"},{"os":"macos-latest","runner_label":"macos","watch_case":"apps","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"300000","watch_poll_ms":"300","watch_command_timeout_ms":"900000"},{"os":"windows-latest","runner_label":"windows","watch_case":"uni","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"mpx","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"uni-app-vue3-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"weapp-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"demo","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":80,"watch_timeout_ms":"540000","watch_poll_ms":"360","watch_command_timeout_ms":"1500000"},{"os":"windows-latest","runner_label":"windows","watch_case":"apps","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":50,"watch_timeout_ms":"360000","watch_poll_ms":"360","watch_command_timeout_ms":"1080000"}]' || '[{"os":"macos-latest","runner_label":"macos","watch_case":"uni","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"mpx","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"uni-app-vue3-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"macos-latest","runner_label":"macos","watch_case":"weapp-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":35,"watch_timeout_ms":"240000","watch_poll_ms":"280","watch_command_timeout_ms":"600000"},{"os":"windows-latest","runner_label":"windows","watch_case":"uni","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"mpx","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"uni-app-vue3-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"},{"os":"windows-latest","runner_label":"windows","watch_case":"weapp-vite","round_profile":"issue33","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"280000","watch_poll_ms":"320","watch_command_timeout_ms":"720000"}]') }} runs-on: ${{ matrix.os }} timeout-minutes: ${{ matrix.timeout_minutes }} @@ -180,92 +130,11 @@ jobs: nightly-full-regression: name: e2e-watch-nightly (${{ matrix.runner_label }}-node22-${{ matrix.watch_case }}-${{ matrix.round_profile }}) - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' strategy: fail-fast: false matrix: - include: - - os: ubuntu-latest - runner_label: ubuntu - watch_case: all - round_profile: default - watch_save_snapshots: '1' - timeout_minutes: 60 - watch_timeout_ms: '480000' - watch_poll_ms: '240' - watch_command_timeout_ms: '1200000' - - os: macos-latest - runner_label: macos - watch_case: all - round_profile: default - watch_save_snapshots: '1' - timeout_minutes: 80 - watch_timeout_ms: '600000' - watch_poll_ms: '340' - watch_command_timeout_ms: '1500000' - - os: windows-latest - runner_label: windows - watch_case: all - round_profile: default - watch_save_snapshots: '1' - timeout_minutes: 95 - watch_timeout_ms: '720000' - watch_poll_ms: '400' - watch_command_timeout_ms: '1800000' - - os: ubuntu-latest - runner_label: ubuntu - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 35 - watch_timeout_ms: '240000' - watch_poll_ms: '220' - watch_command_timeout_ms: '720000' - - os: ubuntu-latest - runner_label: ubuntu - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 35 - watch_timeout_ms: '240000' - watch_poll_ms: '220' - watch_command_timeout_ms: '720000' - - os: macos-latest - runner_label: macos - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 45 - watch_timeout_ms: '300000' - watch_poll_ms: '300' - watch_command_timeout_ms: '900000' - - os: macos-latest - runner_label: macos - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 45 - watch_timeout_ms: '300000' - watch_poll_ms: '300' - watch_command_timeout_ms: '900000' - - os: windows-latest - runner_label: windows - watch_case: uni-app-vue3-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 55 - watch_timeout_ms: '360000' - watch_poll_ms: '360' - watch_command_timeout_ms: '1080000' - - os: windows-latest - runner_label: windows - watch_case: weapp-vite - round_profile: issue33 - watch_save_snapshots: '1' - timeout_minutes: 55 - watch_timeout_ms: '360000' - watch_poll_ms: '360' - watch_command_timeout_ms: '1080000' + include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '[{"os":"macos-latest","runner_label":"macos","watch_case":"all","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":80,"watch_timeout_ms":"600000","watch_poll_ms":"340","watch_command_timeout_ms":"1500000"},{"os":"windows-latest","runner_label":"windows","watch_case":"all","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":95,"watch_timeout_ms":"720000","watch_poll_ms":"400","watch_command_timeout_ms":"1800000"},{"os":"macos-latest","runner_label":"macos","watch_case":"demo","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":65,"watch_timeout_ms":"420000","watch_poll_ms":"300","watch_command_timeout_ms":"1200000"},{"os":"windows-latest","runner_label":"windows","watch_case":"demo","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":80,"watch_timeout_ms":"540000","watch_poll_ms":"360","watch_command_timeout_ms":"1500000"},{"os":"macos-latest","runner_label":"macos","watch_case":"apps","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":40,"watch_timeout_ms":"300000","watch_poll_ms":"300","watch_command_timeout_ms":"900000"},{"os":"windows-latest","runner_label":"windows","watch_case":"apps","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":50,"watch_timeout_ms":"360000","watch_poll_ms":"360","watch_command_timeout_ms":"1080000"}]' || '[{"os":"macos-latest","runner_label":"macos","watch_case":"all","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":80,"watch_timeout_ms":"600000","watch_poll_ms":"340","watch_command_timeout_ms":"1500000"},{"os":"windows-latest","runner_label":"windows","watch_case":"all","round_profile":"default","watch_save_snapshots":"1","timeout_minutes":95,"watch_timeout_ms":"720000","watch_poll_ms":"400","watch_command_timeout_ms":"1800000"}]') }} runs-on: ${{ matrix.os }} timeout-minutes: ${{ matrix.timeout_minutes }} diff --git a/.github/workflows/website-seo-quality.yml b/.github/workflows/website-seo-quality.yml new file mode 100644 index 000000000..fd85707ab --- /dev/null +++ b/.github/workflows/website-seo-quality.yml @@ -0,0 +1,49 @@ +name: Website SEO Quality + +on: + push: + branches: + - main + paths: + - .github/workflows/website-seo-quality.yml + - 'website/**' + workflow_dispatch: + +permissions: + contents: read + +jobs: + seo-quality: + name: SEO Quality Gate + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out code + uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + + - name: Setup Node.js environment + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: SEO Quality Gate (website) + run: pnpm --filter @weapp-tailwindcss/website seo:quality:strict + + - name: SEO Status Note + if: always() + run: | + { + echo "## Website SEO Quality" + echo "- 本工作流独立执行 website SEO 严格门禁。" + echo "- 当前执行命令:\`pnpm --filter @weapp-tailwindcss/website seo:quality:strict\`。" + echo "- 触发条件:主分支推送且命中 website 目录变更,或手动触发。" + echo "- 该工作流不再参与 PR 检查,避免 SEO 存量问题阻塞主 CI。" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index b4510cc69..88c004137 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,4 @@ website/tests/index.spec.ts-snapshots # Local Playwright artifacts /.playwright/ /.playwright-cli/ +.tmp/ diff --git a/apps/AGENTS.md b/apps/AGENTS.md index 0801d6396..bb69218e3 100644 --- a/apps/AGENTS.md +++ b/apps/AGENTS.md @@ -1,18 +1,22 @@ # Workspace Guidelines (`apps/*`) ## 适用范围 + - 本文件适用于 `apps/*` 下所有应用型子项目。 - `apps/*` 主要用于集成验证与示例运行,不是核心库代码的首选承载位置。 ## 通用原则 + - 修改 app 时,优先保持“可运行 + 可复现”,不要引入与验证目标无关的复杂改造。 - 涉及 weapp 相关 app,通常依赖 `postinstall: weapp-tw patch`;调整依赖脚本时需确认 patch 链路不被破坏。 - 当 app 仅用于复现问题时,保持最小修改面并在提交说明中写明复现目标。 ## 常见命令约定 + - 优先使用该 app 的本地脚本(`dev`、`build`、`open` 等)。 - 不同框架命令差异较大(`weapp-vite`、`taro`、`uni`、`vite`),避免跨项目复制命令。 ## 测试与回归 + - 涉及构建行为变更时,至少验证一个真实目标端构建成功(如 `weapp`)。 - 若改动用于验证核心包修复,建议补最小运行说明或脚本,方便复测。 diff --git a/apps/react-app/package.json b/apps/react-app/package.json index 11c15eefc..96b26c4e5 100644 --- a/apps/react-app/package.json +++ b/apps/react-app/package.json @@ -27,10 +27,10 @@ "@types/node": "catalog:typesNode2410", "@types/react": "catalog:react19", "@types/react-dom": "catalog:react19", - "@vitejs/plugin-react": "^5.1.4", + "@vitejs/plugin-react": "^5.2.0", "@weapp-tailwindcss/variants": "workspace:*", "babel-plugin-react-compiler": "^1.0.0", - "eslint": "^10.0.2", + "eslint": "^10.0.3", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", diff --git a/apps/rsmax-app-ts/README.md b/apps/rsmax-app-ts/README.md index 6ef97e447..e788b8147 100644 --- a/apps/rsmax-app-ts/README.md +++ b/apps/rsmax-app-ts/README.md @@ -9,11 +9,15 @@ ```bash npm install ``` + or + ```bash yarn ``` + or + ```bash pnpm install ``` diff --git a/apps/tailwindcss-weapp/package.json b/apps/tailwindcss-weapp/package.json index 4ba137166..962742ff9 100644 --- a/apps/tailwindcss-weapp/package.json +++ b/apps/tailwindcss-weapp/package.json @@ -74,14 +74,14 @@ "@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001", "@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001", "@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001", - "@vue/shared": "^3.5.29", + "@vue/shared": "^3.5.30", "clipboard": "^2.0.11", - "dayjs": "^1.11.12", + "dayjs": "^1.11.20", "pinia": "~2.3.1", "tailwindcss-core-plugins-extractor": "^0.2.0", "uni-app-mp-html": "^2.5.0", "uview-plus": "^3.7.13", - "vue": "^3.5.29", + "vue": "^3.5.30", "vue-i18n": "^9.14.5" }, "devDependencies": { @@ -96,10 +96,10 @@ "@iconify-json/mdi": "^1.2.3", "@iconify-json/ri": "^1.2.10", "@iconify-json/svg-spinners": "^1.2.2", - "@vue/runtime-core": "^3.5.29", + "@vue/runtime-core": "^3.5.30", "autoprefixer": "^10.4.27", "indent-string": "^5.0.0", - "miniprogram-api-typings": "^5.1.1", + "miniprogram-api-typings": "^5.1.2", "postcss": "^8.5.8", "prettier": "^3.8.1", "prettier-plugin-tailwindcss": "^0.7.2", @@ -109,7 +109,7 @@ "unplugin-auto-import": "^19.1.1", "vite": "5.2.8", "vue-eslint-parser": "^10.4.0", - "weapp-ide-cli": "^5.1.0", + "weapp-ide-cli": "^5.1.1", "weapp-tailwindcss": "workspace:*" } } diff --git a/apps/tailwindcss-weapp/src/components/ConfigProvider.vue b/apps/tailwindcss-weapp/src/components/ConfigProvider.vue index 3ed3898e6..2ba63639c 100644 --- a/apps/tailwindcss-weapp/src/components/ConfigProvider.vue +++ b/apps/tailwindcss-weapp/src/components/ConfigProvider.vue @@ -10,7 +10,7 @@ const props = defineProps({ }) const styleObj = computed(() => { - return Object.assign({}, {}, props.vars) + return { ...{}, ...props.vars } }) diff --git a/apps/tailwindcss-weapp/src/components/FloatButton.vue b/apps/tailwindcss-weapp/src/components/FloatButton.vue index c242505eb..fb1488d3d 100644 --- a/apps/tailwindcss-weapp/src/components/FloatButton.vue +++ b/apps/tailwindcss-weapp/src/components/FloatButton.vue @@ -1,6 +1,6 @@