@weapp-tailwindcss/cli 5.4.1 → 5.5.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 +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/bin.cjs +1 -1
- package/dist/bin.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-CrXWQNny.cjs → src-C4Y7BTX1.cjs} +220 -9
- package/dist/{src-BfHGXT3j.js → src-Dpl0bugL.js} +220 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ const exitCode = await runCli(['-i', 'src/input.css', '-o', 'dist/output.css'])
|
|
|
53
53
|
|
|
54
54
|
Importing the package does not execute the CLI. `runCli` follows the same stdout, stderr, and `process.exitCode` behavior as the binary.
|
|
55
55
|
|
|
56
|
+
Use `weapp-tw doctor` when integrating or upgrading a project. It only reports duplicate Tailwind generation when a configuration actually references `@tailwindcss/vite` or `@tailwindcss/postcss`; a plain PostCSS config is not treated as an error.
|
|
57
|
+
|
|
56
58
|
## Documentation
|
|
57
59
|
|
|
58
60
|
See the [CLI guide](https://tw.weapp.dev/docs/tools/weapp-tw-cli) for all options, stdin/stdout, source maps, watch mode, and `canonicalize`.
|
package/README.zh-CN.md
CHANGED
|
@@ -56,3 +56,5 @@ const exitCode = await runCli(['-i', 'src/input.css', '-o', 'dist/output.css'])
|
|
|
56
56
|
## 文档
|
|
57
57
|
|
|
58
58
|
完整参数、stdin/stdout、source map、watch 与 `canonicalize` 用法见 [CLI 使用指南](https://tw.weapp.dev/zh-cn/docs/tools/weapp-tw-cli)。
|
|
59
|
+
|
|
60
|
+
接入或升级排障时,可以运行 `weapp-tw doctor`。它会检查实际配置中是否注册了官方 Tailwind 生成器;仅存在普通 PostCSS 配置不会触发重复生成警告。
|
package/dist/bin.cjs
CHANGED
package/dist/bin.js
CHANGED
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as runCli } from "./src-
|
|
1
|
+
import { t as runCli } from "./src-Dpl0bugL.js";
|
|
2
2
|
export { runCli };
|
|
@@ -475,7 +475,7 @@ async function runTailwindCli(rawArgv) {
|
|
|
475
475
|
}
|
|
476
476
|
//#endregion
|
|
477
477
|
//#region package.json
|
|
478
|
-
var version = "5.
|
|
478
|
+
var version = "5.5.0";
|
|
479
479
|
//#endregion
|
|
480
480
|
//#region src/constants.ts
|
|
481
481
|
const WEAPP_TW_REQUIRED_NODE_VERSION_RANGE = "^22.18.0 || >=24.11.0";
|
|
@@ -522,6 +522,90 @@ const FRAMEWORK_DEPS = [
|
|
|
522
522
|
["remax", "Remax"]
|
|
523
523
|
];
|
|
524
524
|
//#endregion
|
|
525
|
+
//#region src/doctor/semantics.ts
|
|
526
|
+
function readConfigText(cwd, file) {
|
|
527
|
+
if (!file) return;
|
|
528
|
+
try {
|
|
529
|
+
return (0, node_fs.readFileSync)(node_path.default.join(cwd, file), "utf8");
|
|
530
|
+
} catch {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function stripConfigComments(source) {
|
|
535
|
+
return source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/(?<!:)\/\/.*$/gmu, "");
|
|
536
|
+
}
|
|
537
|
+
function findConfiguredTailwindOwners(cwd, files) {
|
|
538
|
+
const owners = /* @__PURE__ */ new Set();
|
|
539
|
+
const evidence = [];
|
|
540
|
+
for (const file of files) {
|
|
541
|
+
const source = readConfigText(cwd, file);
|
|
542
|
+
if (!source) continue;
|
|
543
|
+
const config = stripConfigComments(source);
|
|
544
|
+
if (/['"]@tailwindcss\/vite['"]/u.test(config)) {
|
|
545
|
+
owners.add("vite");
|
|
546
|
+
evidence.push(file);
|
|
547
|
+
}
|
|
548
|
+
if (/['"]@tailwindcss\/postcss['"]/u.test(config)) {
|
|
549
|
+
owners.add("postcss");
|
|
550
|
+
evidence.push(file);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
owners,
|
|
555
|
+
evidence: [...new Set(evidence)]
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function extractConfiguredCssEntries(source) {
|
|
559
|
+
if (!source) return [];
|
|
560
|
+
const match = stripConfigComments(source).match(/cssEntries\s*:\s*\[([\s\S]*?)\]/u);
|
|
561
|
+
if (!match?.[1]) return [];
|
|
562
|
+
return [...match[1].matchAll(/["']([^"']+\.css)["']/gu)].map((item) => item[1]).filter(Boolean);
|
|
563
|
+
}
|
|
564
|
+
function extractConfiguredOption(source, option) {
|
|
565
|
+
if (!source) return;
|
|
566
|
+
return stripConfigComments(source).match(new RegExp(`\\b${option}\\s*:\\s*["']([^"']+)["']`, "u"))?.[1];
|
|
567
|
+
}
|
|
568
|
+
function extractGeneratorTarget(source) {
|
|
569
|
+
if (!source) return;
|
|
570
|
+
const config = stripConfigComments(source);
|
|
571
|
+
const generator = config.match(/generator\s*:\s*\{([\s\S]*?)\}/u)?.[1];
|
|
572
|
+
return extractConfiguredOption(generator, "target") ?? extractConfiguredOption(config, "target");
|
|
573
|
+
}
|
|
574
|
+
function extractCssSources(source) {
|
|
575
|
+
const directives = [];
|
|
576
|
+
for (const match of source.matchAll(/@source\s+(?:(not)\s+)?(?:(inline)\s*\(\s*)?["']([^"']*)["']/gu)) {
|
|
577
|
+
const value = match[3]?.trim();
|
|
578
|
+
if (value) directives.push({
|
|
579
|
+
value,
|
|
580
|
+
excluded: Boolean(match[1]),
|
|
581
|
+
inline: Boolean(match[2])
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return directives;
|
|
585
|
+
}
|
|
586
|
+
function resolveSourceProbePath(cssFile, source) {
|
|
587
|
+
const wildcard = source.search(/[!*?[{]/u);
|
|
588
|
+
const prefix = wildcard >= 0 ? source.slice(0, wildcard) : source;
|
|
589
|
+
const directory = wildcard >= 0 ? node_path.default.dirname(prefix) : prefix;
|
|
590
|
+
return node_path.default.resolve(node_path.default.dirname(cssFile), directory || ".");
|
|
591
|
+
}
|
|
592
|
+
const SUPPORTED_APP_TYPES = /* @__PURE__ */ new Set([
|
|
593
|
+
"uni-app",
|
|
594
|
+
"uni-app-vite",
|
|
595
|
+
"uni-app-x",
|
|
596
|
+
"taro",
|
|
597
|
+
"remax",
|
|
598
|
+
"native",
|
|
599
|
+
"kbone",
|
|
600
|
+
"mpx",
|
|
601
|
+
"weapp-vite"
|
|
602
|
+
]);
|
|
603
|
+
const SUPPORTED_GENERATOR_TARGETS = /* @__PURE__ */ new Set([
|
|
604
|
+
"web",
|
|
605
|
+
"weapp",
|
|
606
|
+
"app"
|
|
607
|
+
]);
|
|
608
|
+
//#endregion
|
|
525
609
|
//#region src/doctor.ts
|
|
526
610
|
function tryReadJson(file) {
|
|
527
611
|
try {
|
|
@@ -561,7 +645,11 @@ function detectFrameworks(deps) {
|
|
|
561
645
|
return FRAMEWORK_DEPS.filter(([dependency]) => dependency in deps).map(([, label]) => label);
|
|
562
646
|
}
|
|
563
647
|
function addCheck(checks, check) {
|
|
564
|
-
checks.push(
|
|
648
|
+
checks.push({
|
|
649
|
+
...check,
|
|
650
|
+
code: check.code ?? check.id,
|
|
651
|
+
evidence: check.evidence ?? []
|
|
652
|
+
});
|
|
565
653
|
}
|
|
566
654
|
function summarizeChecks(checks) {
|
|
567
655
|
return checks.reduce((summary, check) => {
|
|
@@ -593,13 +681,37 @@ function createDoctorReport(options = {}) {
|
|
|
593
681
|
const checks = [];
|
|
594
682
|
const packageManager = detectPackageManager(cwd, pkg);
|
|
595
683
|
const frameworks = detectFrameworks(deps);
|
|
596
|
-
const tailwindcssVersion = readDependencyVersion(cwd, "tailwindcss");
|
|
597
|
-
const weappTailwindcssVersion = readDependencyVersion(cwd, "weapp-tailwindcss");
|
|
684
|
+
const tailwindcssVersion = hasDependency(deps, "tailwindcss") ? readDependencyVersion(cwd, "tailwindcss") : void 0;
|
|
685
|
+
const weappTailwindcssVersion = hasDependency(deps, "weapp-tailwindcss") ? readDependencyVersion(cwd, "weapp-tailwindcss") : void 0;
|
|
598
686
|
const tailwindMajor = getMajorVersion(tailwindcssVersion) ?? getDependencyMajor(deps, "tailwindcss");
|
|
599
687
|
const tailwindConfig = findFirstExisting(cwd, CONFIG_FILES.tailwind);
|
|
600
688
|
const postcssConfig = findFirstExisting(cwd, CONFIG_FILES.postcss);
|
|
601
689
|
const viteConfig = findFirstExisting(cwd, CONFIG_FILES.vite);
|
|
602
690
|
const webpackConfig = findFirstExisting(cwd, CONFIG_FILES.webpack);
|
|
691
|
+
const tailwindOwners = findConfiguredTailwindOwners(cwd, [
|
|
692
|
+
postcssConfig,
|
|
693
|
+
viteConfig,
|
|
694
|
+
webpackConfig
|
|
695
|
+
]);
|
|
696
|
+
const cssEntries = [
|
|
697
|
+
postcssConfig,
|
|
698
|
+
viteConfig,
|
|
699
|
+
webpackConfig
|
|
700
|
+
].map((file) => ({
|
|
701
|
+
file,
|
|
702
|
+
source: readConfigText(cwd, file)
|
|
703
|
+
})).flatMap(({ file, source }) => extractConfiguredCssEntries(source).map((entry) => ({
|
|
704
|
+
file,
|
|
705
|
+
entry
|
|
706
|
+
})));
|
|
707
|
+
const buildConfigs = [
|
|
708
|
+
postcssConfig,
|
|
709
|
+
viteConfig,
|
|
710
|
+
webpackConfig
|
|
711
|
+
].map((file) => ({
|
|
712
|
+
file,
|
|
713
|
+
source: readConfigText(cwd, file)
|
|
714
|
+
})).filter((item) => Boolean(item.file && item.source));
|
|
603
715
|
addCheck(checks, pkg ? {
|
|
604
716
|
id: "package-json",
|
|
605
717
|
title: "package.json",
|
|
@@ -683,13 +795,112 @@ function createDoctorReport(options = {}) {
|
|
|
683
795
|
message: "未检测到 postcss.config.*。",
|
|
684
796
|
suggestion: "如果通过 PostCSS 接入,请补充 postcss.config.*;如果通过 Vite/Taro 插件接入,可忽略此项。"
|
|
685
797
|
});
|
|
686
|
-
if (tailwindMajor === 4 &&
|
|
687
|
-
id: "tailwindcss-
|
|
688
|
-
title: "Tailwind
|
|
798
|
+
if (tailwindMajor === 4 && tailwindOwners.owners.size > 0) addCheck(checks, {
|
|
799
|
+
id: "tailwindcss-generator-owner",
|
|
800
|
+
title: "Tailwind 生成器归属",
|
|
689
801
|
status: "warn",
|
|
690
|
-
message:
|
|
691
|
-
suggestion: "
|
|
802
|
+
message: `检测到官方 Tailwind ${[...tailwindOwners.owners].join(" 与 ")} 生成器配置。生成模式下不要与 WeappTailwindcss 同时运行两套 Tailwind 生成器。`,
|
|
803
|
+
suggestion: "移除 @tailwindcss/vite 或 @tailwindcss/postcss 的生成配置,仅保留 WeappTailwindcss;PostCSS 中可以继续保留其他业务插件。",
|
|
804
|
+
code: "duplicate-tailwind-generator",
|
|
805
|
+
evidence: tailwindOwners.evidence
|
|
692
806
|
});
|
|
807
|
+
if (tailwindMajor === 4 && cssEntries.length > 0) {
|
|
808
|
+
const missingEntries = cssEntries.filter(({ entry }) => !(0, node_fs.existsSync)(node_path.default.resolve(cwd, entry)));
|
|
809
|
+
const entriesWithoutTailwindImport = cssEntries.filter(({ entry }) => {
|
|
810
|
+
if (!(0, node_fs.existsSync)(node_path.default.resolve(cwd, entry))) return false;
|
|
811
|
+
try {
|
|
812
|
+
const source = (0, node_fs.readFileSync)(node_path.default.resolve(cwd, entry), "utf8");
|
|
813
|
+
return !/@import\s+["']tailwindcss["']/u.test(source) && !/@import\s+["']tailwindcss\//u.test(source);
|
|
814
|
+
} catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
if (missingEntries.length > 0) addCheck(checks, {
|
|
819
|
+
id: "tailwind-css-entry",
|
|
820
|
+
title: "Tailwind CSS 入口",
|
|
821
|
+
status: "error",
|
|
822
|
+
message: `配置了不存在的 cssEntries:${missingEntries.map((item) => item.entry).join(", ")}。`,
|
|
823
|
+
suggestion: "请将 cssEntries 改为项目根目录解析出的绝对路径,或确认入口文件已创建。",
|
|
824
|
+
code: "missing-css-entry",
|
|
825
|
+
evidence: missingEntries.map((item) => item.file).filter((file) => Boolean(file))
|
|
826
|
+
});
|
|
827
|
+
if (entriesWithoutTailwindImport.length > 0) addCheck(checks, {
|
|
828
|
+
id: "tailwind-css-entry-import",
|
|
829
|
+
title: "Tailwind CSS 入口内容",
|
|
830
|
+
status: "warn",
|
|
831
|
+
message: `cssEntries 指向的文件未发现 @import "tailwindcss":${entriesWithoutTailwindImport.map((item) => item.entry).join(", ")}。`,
|
|
832
|
+
suggestion: "请在纯 CSS 入口中加入 @import \"tailwindcss\",并确保该文件被应用构建图实际引入。",
|
|
833
|
+
code: "missing-tailwind-import",
|
|
834
|
+
evidence: entriesWithoutTailwindImport.map((item) => node_path.default.resolve(cwd, item.entry))
|
|
835
|
+
});
|
|
836
|
+
cssEntries.flatMap(({ entry }) => {
|
|
837
|
+
const cssFile = node_path.default.resolve(cwd, entry);
|
|
838
|
+
if (!(0, node_fs.existsSync)(cssFile)) return [];
|
|
839
|
+
let source;
|
|
840
|
+
try {
|
|
841
|
+
source = (0, node_fs.readFileSync)(cssFile, "utf8");
|
|
842
|
+
} catch {
|
|
843
|
+
return [];
|
|
844
|
+
}
|
|
845
|
+
const directives = extractCssSources(source);
|
|
846
|
+
const diagnostics = [];
|
|
847
|
+
if (!directives.some((directive) => !directive.excluded)) diagnostics.push({
|
|
848
|
+
id: "tailwind-css-source",
|
|
849
|
+
title: "Tailwind CSS 来源范围",
|
|
850
|
+
status: "warn",
|
|
851
|
+
message: `${entry} 未发现 @source,Tailwind 可能无法扫描项目源码。`,
|
|
852
|
+
suggestion: "请在 CSS 入口中加入覆盖实际源码目录的 @source;仅配置 cssEntries 不会替代源码扫描声明。",
|
|
853
|
+
code: "missing-tailwind-source",
|
|
854
|
+
evidence: [cssFile]
|
|
855
|
+
});
|
|
856
|
+
for (const directive of directives) {
|
|
857
|
+
if (directive.inline || directive.excluded) continue;
|
|
858
|
+
const probePath = resolveSourceProbePath(cssFile, directive.value);
|
|
859
|
+
if (!(0, node_fs.existsSync)(probePath)) diagnostics.push({
|
|
860
|
+
id: "tailwind-css-source-path",
|
|
861
|
+
title: "Tailwind @source 路径",
|
|
862
|
+
status: "warn",
|
|
863
|
+
message: `@source ${directive.value} 的路径不存在或无法从入口解析。`,
|
|
864
|
+
suggestion: "请按 CSS 入口所在目录修正 @source 相对路径,或确认源码目录已经创建。",
|
|
865
|
+
code: "invalid-tailwind-source",
|
|
866
|
+
evidence: [cssFile, probePath]
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
return diagnostics;
|
|
870
|
+
}).forEach((check) => addCheck(checks, check));
|
|
871
|
+
}
|
|
872
|
+
for (const { file, source } of buildConfigs) {
|
|
873
|
+
const appType = extractConfiguredOption(source, "appType");
|
|
874
|
+
if (appType && !SUPPORTED_APP_TYPES.has(appType)) addCheck(checks, {
|
|
875
|
+
id: "app-type",
|
|
876
|
+
title: "appType 配置",
|
|
877
|
+
status: "error",
|
|
878
|
+
message: `${file} 使用了不受支持的 appType:${appType}。`,
|
|
879
|
+
suggestion: "请改为 uni-app、uni-app-x、taro、mpx、weapp-vite 或其他公开支持的 appType。",
|
|
880
|
+
code: "invalid-app-type",
|
|
881
|
+
evidence: [file]
|
|
882
|
+
});
|
|
883
|
+
const target = extractGeneratorTarget(source);
|
|
884
|
+
if (target && !SUPPORTED_GENERATOR_TARGETS.has(target)) addCheck(checks, {
|
|
885
|
+
id: "generator-target",
|
|
886
|
+
title: "生成目标",
|
|
887
|
+
status: "error",
|
|
888
|
+
message: `${file} 使用了不受支持的 generator.target:${target}。`,
|
|
889
|
+
suggestion: "请使用 web、weapp 或 app;普通 Vite Web 项目推荐使用 web。",
|
|
890
|
+
code: "invalid-generator-target",
|
|
891
|
+
evidence: [file]
|
|
892
|
+
});
|
|
893
|
+
const platform = extractConfiguredOption(source, "platform");
|
|
894
|
+
if (platform && target && (platform === "web" && target !== "web" || platform !== "web" && target === "web")) addCheck(checks, {
|
|
895
|
+
id: "generator-platform",
|
|
896
|
+
title: "生成目标与平台",
|
|
897
|
+
status: "warn",
|
|
898
|
+
message: `${file} 的 platform=${platform} 与 generator.target=${target} 可能不一致。`,
|
|
899
|
+
suggestion: "请让 platform 与 generator.target 指向同一运行端,或删除不必要的显式覆盖以使用自动推断。",
|
|
900
|
+
code: "generator-target-platform-conflict",
|
|
901
|
+
evidence: [file]
|
|
902
|
+
});
|
|
903
|
+
}
|
|
693
904
|
addCheck(checks, frameworks.length > 0 ? {
|
|
694
905
|
id: "framework",
|
|
695
906
|
title: "框架识别",
|
|
@@ -446,7 +446,7 @@ async function runTailwindCli(rawArgv) {
|
|
|
446
446
|
}
|
|
447
447
|
//#endregion
|
|
448
448
|
//#region package.json
|
|
449
|
-
var version = "5.
|
|
449
|
+
var version = "5.5.0";
|
|
450
450
|
//#endregion
|
|
451
451
|
//#region src/constants.ts
|
|
452
452
|
const WEAPP_TW_REQUIRED_NODE_VERSION_RANGE = "^22.18.0 || >=24.11.0";
|
|
@@ -493,6 +493,90 @@ const FRAMEWORK_DEPS = [
|
|
|
493
493
|
["remax", "Remax"]
|
|
494
494
|
];
|
|
495
495
|
//#endregion
|
|
496
|
+
//#region src/doctor/semantics.ts
|
|
497
|
+
function readConfigText(cwd, file) {
|
|
498
|
+
if (!file) return;
|
|
499
|
+
try {
|
|
500
|
+
return readFileSync(path.join(cwd, file), "utf8");
|
|
501
|
+
} catch {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function stripConfigComments(source) {
|
|
506
|
+
return source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/(?<!:)\/\/.*$/gmu, "");
|
|
507
|
+
}
|
|
508
|
+
function findConfiguredTailwindOwners(cwd, files) {
|
|
509
|
+
const owners = /* @__PURE__ */ new Set();
|
|
510
|
+
const evidence = [];
|
|
511
|
+
for (const file of files) {
|
|
512
|
+
const source = readConfigText(cwd, file);
|
|
513
|
+
if (!source) continue;
|
|
514
|
+
const config = stripConfigComments(source);
|
|
515
|
+
if (/['"]@tailwindcss\/vite['"]/u.test(config)) {
|
|
516
|
+
owners.add("vite");
|
|
517
|
+
evidence.push(file);
|
|
518
|
+
}
|
|
519
|
+
if (/['"]@tailwindcss\/postcss['"]/u.test(config)) {
|
|
520
|
+
owners.add("postcss");
|
|
521
|
+
evidence.push(file);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
owners,
|
|
526
|
+
evidence: [...new Set(evidence)]
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function extractConfiguredCssEntries(source) {
|
|
530
|
+
if (!source) return [];
|
|
531
|
+
const match = stripConfigComments(source).match(/cssEntries\s*:\s*\[([\s\S]*?)\]/u);
|
|
532
|
+
if (!match?.[1]) return [];
|
|
533
|
+
return [...match[1].matchAll(/["']([^"']+\.css)["']/gu)].map((item) => item[1]).filter(Boolean);
|
|
534
|
+
}
|
|
535
|
+
function extractConfiguredOption(source, option) {
|
|
536
|
+
if (!source) return;
|
|
537
|
+
return stripConfigComments(source).match(new RegExp(`\\b${option}\\s*:\\s*["']([^"']+)["']`, "u"))?.[1];
|
|
538
|
+
}
|
|
539
|
+
function extractGeneratorTarget(source) {
|
|
540
|
+
if (!source) return;
|
|
541
|
+
const config = stripConfigComments(source);
|
|
542
|
+
const generator = config.match(/generator\s*:\s*\{([\s\S]*?)\}/u)?.[1];
|
|
543
|
+
return extractConfiguredOption(generator, "target") ?? extractConfiguredOption(config, "target");
|
|
544
|
+
}
|
|
545
|
+
function extractCssSources(source) {
|
|
546
|
+
const directives = [];
|
|
547
|
+
for (const match of source.matchAll(/@source\s+(?:(not)\s+)?(?:(inline)\s*\(\s*)?["']([^"']*)["']/gu)) {
|
|
548
|
+
const value = match[3]?.trim();
|
|
549
|
+
if (value) directives.push({
|
|
550
|
+
value,
|
|
551
|
+
excluded: Boolean(match[1]),
|
|
552
|
+
inline: Boolean(match[2])
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return directives;
|
|
556
|
+
}
|
|
557
|
+
function resolveSourceProbePath(cssFile, source) {
|
|
558
|
+
const wildcard = source.search(/[!*?[{]/u);
|
|
559
|
+
const prefix = wildcard >= 0 ? source.slice(0, wildcard) : source;
|
|
560
|
+
const directory = wildcard >= 0 ? path.dirname(prefix) : prefix;
|
|
561
|
+
return path.resolve(path.dirname(cssFile), directory || ".");
|
|
562
|
+
}
|
|
563
|
+
const SUPPORTED_APP_TYPES = /* @__PURE__ */ new Set([
|
|
564
|
+
"uni-app",
|
|
565
|
+
"uni-app-vite",
|
|
566
|
+
"uni-app-x",
|
|
567
|
+
"taro",
|
|
568
|
+
"remax",
|
|
569
|
+
"native",
|
|
570
|
+
"kbone",
|
|
571
|
+
"mpx",
|
|
572
|
+
"weapp-vite"
|
|
573
|
+
]);
|
|
574
|
+
const SUPPORTED_GENERATOR_TARGETS = /* @__PURE__ */ new Set([
|
|
575
|
+
"web",
|
|
576
|
+
"weapp",
|
|
577
|
+
"app"
|
|
578
|
+
]);
|
|
579
|
+
//#endregion
|
|
496
580
|
//#region src/doctor.ts
|
|
497
581
|
function tryReadJson(file) {
|
|
498
582
|
try {
|
|
@@ -532,7 +616,11 @@ function detectFrameworks(deps) {
|
|
|
532
616
|
return FRAMEWORK_DEPS.filter(([dependency]) => dependency in deps).map(([, label]) => label);
|
|
533
617
|
}
|
|
534
618
|
function addCheck(checks, check) {
|
|
535
|
-
checks.push(
|
|
619
|
+
checks.push({
|
|
620
|
+
...check,
|
|
621
|
+
code: check.code ?? check.id,
|
|
622
|
+
evidence: check.evidence ?? []
|
|
623
|
+
});
|
|
536
624
|
}
|
|
537
625
|
function summarizeChecks(checks) {
|
|
538
626
|
return checks.reduce((summary, check) => {
|
|
@@ -564,13 +652,37 @@ function createDoctorReport(options = {}) {
|
|
|
564
652
|
const checks = [];
|
|
565
653
|
const packageManager = detectPackageManager(cwd, pkg);
|
|
566
654
|
const frameworks = detectFrameworks(deps);
|
|
567
|
-
const tailwindcssVersion = readDependencyVersion(cwd, "tailwindcss");
|
|
568
|
-
const weappTailwindcssVersion = readDependencyVersion(cwd, "weapp-tailwindcss");
|
|
655
|
+
const tailwindcssVersion = hasDependency(deps, "tailwindcss") ? readDependencyVersion(cwd, "tailwindcss") : void 0;
|
|
656
|
+
const weappTailwindcssVersion = hasDependency(deps, "weapp-tailwindcss") ? readDependencyVersion(cwd, "weapp-tailwindcss") : void 0;
|
|
569
657
|
const tailwindMajor = getMajorVersion(tailwindcssVersion) ?? getDependencyMajor(deps, "tailwindcss");
|
|
570
658
|
const tailwindConfig = findFirstExisting(cwd, CONFIG_FILES.tailwind);
|
|
571
659
|
const postcssConfig = findFirstExisting(cwd, CONFIG_FILES.postcss);
|
|
572
660
|
const viteConfig = findFirstExisting(cwd, CONFIG_FILES.vite);
|
|
573
661
|
const webpackConfig = findFirstExisting(cwd, CONFIG_FILES.webpack);
|
|
662
|
+
const tailwindOwners = findConfiguredTailwindOwners(cwd, [
|
|
663
|
+
postcssConfig,
|
|
664
|
+
viteConfig,
|
|
665
|
+
webpackConfig
|
|
666
|
+
]);
|
|
667
|
+
const cssEntries = [
|
|
668
|
+
postcssConfig,
|
|
669
|
+
viteConfig,
|
|
670
|
+
webpackConfig
|
|
671
|
+
].map((file) => ({
|
|
672
|
+
file,
|
|
673
|
+
source: readConfigText(cwd, file)
|
|
674
|
+
})).flatMap(({ file, source }) => extractConfiguredCssEntries(source).map((entry) => ({
|
|
675
|
+
file,
|
|
676
|
+
entry
|
|
677
|
+
})));
|
|
678
|
+
const buildConfigs = [
|
|
679
|
+
postcssConfig,
|
|
680
|
+
viteConfig,
|
|
681
|
+
webpackConfig
|
|
682
|
+
].map((file) => ({
|
|
683
|
+
file,
|
|
684
|
+
source: readConfigText(cwd, file)
|
|
685
|
+
})).filter((item) => Boolean(item.file && item.source));
|
|
574
686
|
addCheck(checks, pkg ? {
|
|
575
687
|
id: "package-json",
|
|
576
688
|
title: "package.json",
|
|
@@ -654,13 +766,112 @@ function createDoctorReport(options = {}) {
|
|
|
654
766
|
message: "未检测到 postcss.config.*。",
|
|
655
767
|
suggestion: "如果通过 PostCSS 接入,请补充 postcss.config.*;如果通过 Vite/Taro 插件接入,可忽略此项。"
|
|
656
768
|
});
|
|
657
|
-
if (tailwindMajor === 4 &&
|
|
658
|
-
id: "tailwindcss-
|
|
659
|
-
title: "Tailwind
|
|
769
|
+
if (tailwindMajor === 4 && tailwindOwners.owners.size > 0) addCheck(checks, {
|
|
770
|
+
id: "tailwindcss-generator-owner",
|
|
771
|
+
title: "Tailwind 生成器归属",
|
|
660
772
|
status: "warn",
|
|
661
|
-
message:
|
|
662
|
-
suggestion: "
|
|
773
|
+
message: `检测到官方 Tailwind ${[...tailwindOwners.owners].join(" 与 ")} 生成器配置。生成模式下不要与 WeappTailwindcss 同时运行两套 Tailwind 生成器。`,
|
|
774
|
+
suggestion: "移除 @tailwindcss/vite 或 @tailwindcss/postcss 的生成配置,仅保留 WeappTailwindcss;PostCSS 中可以继续保留其他业务插件。",
|
|
775
|
+
code: "duplicate-tailwind-generator",
|
|
776
|
+
evidence: tailwindOwners.evidence
|
|
663
777
|
});
|
|
778
|
+
if (tailwindMajor === 4 && cssEntries.length > 0) {
|
|
779
|
+
const missingEntries = cssEntries.filter(({ entry }) => !existsSync(path.resolve(cwd, entry)));
|
|
780
|
+
const entriesWithoutTailwindImport = cssEntries.filter(({ entry }) => {
|
|
781
|
+
if (!existsSync(path.resolve(cwd, entry))) return false;
|
|
782
|
+
try {
|
|
783
|
+
const source = readFileSync(path.resolve(cwd, entry), "utf8");
|
|
784
|
+
return !/@import\s+["']tailwindcss["']/u.test(source) && !/@import\s+["']tailwindcss\//u.test(source);
|
|
785
|
+
} catch {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
if (missingEntries.length > 0) addCheck(checks, {
|
|
790
|
+
id: "tailwind-css-entry",
|
|
791
|
+
title: "Tailwind CSS 入口",
|
|
792
|
+
status: "error",
|
|
793
|
+
message: `配置了不存在的 cssEntries:${missingEntries.map((item) => item.entry).join(", ")}。`,
|
|
794
|
+
suggestion: "请将 cssEntries 改为项目根目录解析出的绝对路径,或确认入口文件已创建。",
|
|
795
|
+
code: "missing-css-entry",
|
|
796
|
+
evidence: missingEntries.map((item) => item.file).filter((file) => Boolean(file))
|
|
797
|
+
});
|
|
798
|
+
if (entriesWithoutTailwindImport.length > 0) addCheck(checks, {
|
|
799
|
+
id: "tailwind-css-entry-import",
|
|
800
|
+
title: "Tailwind CSS 入口内容",
|
|
801
|
+
status: "warn",
|
|
802
|
+
message: `cssEntries 指向的文件未发现 @import "tailwindcss":${entriesWithoutTailwindImport.map((item) => item.entry).join(", ")}。`,
|
|
803
|
+
suggestion: "请在纯 CSS 入口中加入 @import \"tailwindcss\",并确保该文件被应用构建图实际引入。",
|
|
804
|
+
code: "missing-tailwind-import",
|
|
805
|
+
evidence: entriesWithoutTailwindImport.map((item) => path.resolve(cwd, item.entry))
|
|
806
|
+
});
|
|
807
|
+
cssEntries.flatMap(({ entry }) => {
|
|
808
|
+
const cssFile = path.resolve(cwd, entry);
|
|
809
|
+
if (!existsSync(cssFile)) return [];
|
|
810
|
+
let source;
|
|
811
|
+
try {
|
|
812
|
+
source = readFileSync(cssFile, "utf8");
|
|
813
|
+
} catch {
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
const directives = extractCssSources(source);
|
|
817
|
+
const diagnostics = [];
|
|
818
|
+
if (!directives.some((directive) => !directive.excluded)) diagnostics.push({
|
|
819
|
+
id: "tailwind-css-source",
|
|
820
|
+
title: "Tailwind CSS 来源范围",
|
|
821
|
+
status: "warn",
|
|
822
|
+
message: `${entry} 未发现 @source,Tailwind 可能无法扫描项目源码。`,
|
|
823
|
+
suggestion: "请在 CSS 入口中加入覆盖实际源码目录的 @source;仅配置 cssEntries 不会替代源码扫描声明。",
|
|
824
|
+
code: "missing-tailwind-source",
|
|
825
|
+
evidence: [cssFile]
|
|
826
|
+
});
|
|
827
|
+
for (const directive of directives) {
|
|
828
|
+
if (directive.inline || directive.excluded) continue;
|
|
829
|
+
const probePath = resolveSourceProbePath(cssFile, directive.value);
|
|
830
|
+
if (!existsSync(probePath)) diagnostics.push({
|
|
831
|
+
id: "tailwind-css-source-path",
|
|
832
|
+
title: "Tailwind @source 路径",
|
|
833
|
+
status: "warn",
|
|
834
|
+
message: `@source ${directive.value} 的路径不存在或无法从入口解析。`,
|
|
835
|
+
suggestion: "请按 CSS 入口所在目录修正 @source 相对路径,或确认源码目录已经创建。",
|
|
836
|
+
code: "invalid-tailwind-source",
|
|
837
|
+
evidence: [cssFile, probePath]
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return diagnostics;
|
|
841
|
+
}).forEach((check) => addCheck(checks, check));
|
|
842
|
+
}
|
|
843
|
+
for (const { file, source } of buildConfigs) {
|
|
844
|
+
const appType = extractConfiguredOption(source, "appType");
|
|
845
|
+
if (appType && !SUPPORTED_APP_TYPES.has(appType)) addCheck(checks, {
|
|
846
|
+
id: "app-type",
|
|
847
|
+
title: "appType 配置",
|
|
848
|
+
status: "error",
|
|
849
|
+
message: `${file} 使用了不受支持的 appType:${appType}。`,
|
|
850
|
+
suggestion: "请改为 uni-app、uni-app-x、taro、mpx、weapp-vite 或其他公开支持的 appType。",
|
|
851
|
+
code: "invalid-app-type",
|
|
852
|
+
evidence: [file]
|
|
853
|
+
});
|
|
854
|
+
const target = extractGeneratorTarget(source);
|
|
855
|
+
if (target && !SUPPORTED_GENERATOR_TARGETS.has(target)) addCheck(checks, {
|
|
856
|
+
id: "generator-target",
|
|
857
|
+
title: "生成目标",
|
|
858
|
+
status: "error",
|
|
859
|
+
message: `${file} 使用了不受支持的 generator.target:${target}。`,
|
|
860
|
+
suggestion: "请使用 web、weapp 或 app;普通 Vite Web 项目推荐使用 web。",
|
|
861
|
+
code: "invalid-generator-target",
|
|
862
|
+
evidence: [file]
|
|
863
|
+
});
|
|
864
|
+
const platform = extractConfiguredOption(source, "platform");
|
|
865
|
+
if (platform && target && (platform === "web" && target !== "web" || platform !== "web" && target === "web")) addCheck(checks, {
|
|
866
|
+
id: "generator-platform",
|
|
867
|
+
title: "生成目标与平台",
|
|
868
|
+
status: "warn",
|
|
869
|
+
message: `${file} 的 platform=${platform} 与 generator.target=${target} 可能不一致。`,
|
|
870
|
+
suggestion: "请让 platform 与 generator.target 指向同一运行端,或删除不必要的显式覆盖以使用自动推断。",
|
|
871
|
+
code: "generator-target-platform-conflict",
|
|
872
|
+
evidence: [file]
|
|
873
|
+
});
|
|
874
|
+
}
|
|
664
875
|
addCheck(checks, frameworks.length > 0 ? {
|
|
665
876
|
id: "framework",
|
|
666
877
|
title: "框架识别",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weapp-tailwindcss/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.5.0",
|
|
5
5
|
"description": "Cross-platform Tailwind CSS CLI for Web and mini-program builds. 面向 Web 与小程序构建的跨端 Tailwind CSS CLI。",
|
|
6
6
|
"author": "ice breaker <1324318532@qq.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"fast-glob": "^3.3.3",
|
|
56
56
|
"lightningcss": "^1.33.0",
|
|
57
57
|
"semver": "~7.8.5",
|
|
58
|
-
"weapp-tailwindcss": "5.
|
|
58
|
+
"weapp-tailwindcss": "5.5.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|