├── examples
├── npm-pkg
│ ├── bin2
│ │ └── test.html
│ ├── dist1
│ │ └── test.js
│ ├── dist2
│ │ └── test.js
│ ├── src
│ │ ├── cjsModule.ts
│ │ ├── index.ts
│ │ ├── cjsImportEsm.ts
│ │ └── awaitImport.ts
│ ├── .doctor.ts
│ └── package.json
├── web-tools
│ ├── .doctor.ts
│ └── package.json
├── diy
│ ├── .doctor.ts
│ └── package.json
└── user-preset
│ └── package.json
├── .npmrc
├── packages
├── create-doctor
│ ├── templates
│ │ ├── feature
│ │ │ ├── README.md
│ │ │ ├── .npmrc
│ │ │ ├── src
│ │ │ │ ├── constants.ts.tpl
│ │ │ │ ├── features
│ │ │ │ │ ├── index.ts.tpl
│ │ │ │ │ └── check.ts.tpl
│ │ │ │ ├── defineConfig.ts.tpl
│ │ │ │ ├── index.ts.tpl
│ │ │ │ └── type.ts.tpl
│ │ │ ├── .gitignore
│ │ │ ├── .fatherrc.ts
│ │ │ ├── tsconfig.json
│ │ │ └── package.json.tpl
│ │ └── preset
│ │ │ ├── README.md
│ │ │ ├── .npmrc
│ │ │ ├── src
│ │ │ ├── constants.ts.tpl
│ │ │ ├── features
│ │ │ │ ├── index.ts.tpl
│ │ │ │ └── check.ts.tpl
│ │ │ ├── defineConfig.ts.tpl
│ │ │ ├── commands
│ │ │ │ └── command.ts.tpl
│ │ │ ├── index.ts.tpl
│ │ │ └── type.ts.tpl
│ │ │ ├── .gitignore
│ │ │ ├── .fatherrc.ts
│ │ │ ├── tsconfig.json
│ │ │ └── package.json.tpl
│ ├── .npmrc
│ ├── bin
│ │ └── create-doctor.js
│ ├── .fatherrc.ts
│ ├── README.md
│ ├── src
│ │ ├── utils.ts
│ │ ├── cli.ts
│ │ └── index.ts
│ └── package.json
├── core
│ ├── .npmrc
│ ├── .fatherrc.ts
│ ├── bin
│ │ └── doctor.js
│ ├── src
│ │ ├── index.ts
│ │ ├── constants.ts
│ │ ├── types.ts
│ │ ├── cli
│ │ │ ├── node.ts
│ │ │ └── cli.ts
│ │ ├── service
│ │ │ └── service.ts
│ │ ├── generateDiy.ts
│ │ ├── config
│ │ │ └── index.ts
│ │ ├── utils.ts
│ │ └── generatePreset.ts
│ └── package.json
├── diy
│ ├── .npmrc
│ ├── .fatherrc.ts
│ ├── src
│ │ └── index.ts
│ └── package.json
├── npm-pkg
│ ├── .npmrc
│ ├── src
│ │ ├── constants.ts
│ │ ├── defineConfig.ts
│ │ ├── features
│ │ │ ├── index.ts
│ │ │ ├── preferPackFiles.ts
│ │ │ ├── depInPeerDependencies.ts
│ │ │ ├── cjsImportEsm.ts
│ │ │ └── checkPkgFilesExist.ts
│ │ ├── index.ts
│ │ ├── type.ts
│ │ └── commands
│ │ │ └── npm-pkg.ts
│ ├── .fatherrc.ts
│ └── package.json
├── utils
│ ├── .npmrc
│ ├── src
│ │ ├── features
│ │ │ ├── getIntNodeVersion.ts
│ │ │ ├── getDefaultBrowser.ts
│ │ │ ├── index.ts
│ │ │ ├── IsGitPersistent.ts
│ │ │ ├── isChromeInstalled.ts
│ │ │ └── scanningParser.ts
│ │ ├── index.ts
│ │ ├── types
│ │ │ └── index.ts
│ │ ├── exec.ts
│ │ ├── constants.ts
│ │ ├── cache.ts
│ │ ├── ui.ts
│ │ └── common.ts
│ ├── .fatherrc.ts
│ └── package.json
└── web-tools
│ ├── .npmrc
│ ├── src
│ ├── constants.ts
│ ├── defineConfig.ts
│ ├── features
│ │ ├── index.ts
│ │ ├── checkChrome.ts
│ │ ├── checkNode.ts
│ │ ├── checkDefaultBrowser.ts
│ │ └── checkGitSSHKeyPersistent.ts
│ ├── commands
│ │ └── web-tools.ts
│ ├── index.ts
│ └── type.ts
│ ├── .fatherrc.ts
│ └── package.json
├── .prettierignore
├── website
├── tsconfig.json
├── docs
│ ├── index.en-US.md
│ ├── index.zh-CN.md
│ ├── changelog.md
│ ├── config
│ │ ├── web-tools.zh-CN.md
│ │ ├── npm-pkg.zh-CN.md
│ │ └── index.en-US.md
│ └── guide
│ │ ├── index.en-US.md
│ │ ├── start-web-tools.zh-CN.md
│ │ ├── index.zh-CN.md
│ │ ├── feature.zh-CN.md
│ │ ├── feature-feature.zh-CN.md
│ │ ├── feature-preset.zh-CN.md
│ │ ├── start-common.zh-CN.md
│ │ ├── start-complex-app.zh-CN.md
│ │ └── start-npm-pkg.zh-CN.md
├── package.json
├── .dumirc.ts
└── config
│ ├── features.ts
│ └── footer.tsx
├── pnpm-workspace.yaml
├── .husky
└── pre-commit
├── .prettierrc.js
├── .gitignore
├── .fatherrc.base.ts
├── vitest.config.ts
├── alias.ts
├── scripts
└── preinstall.js
├── test
└── npm-pkg.test.ts
├── .github
└── workflows
│ ├── issue-label.yaml
│ ├── ci.yaml
│ └── release.yaml
├── tsconfig.json
├── package.json
└── README.md
/examples/npm-pkg/bin2/test.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/npm-pkg/dist1/test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/npm-pkg/dist2/test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/core/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/packages/diy/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/packages/npm-pkg/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/packages/utils/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .husky
2 | .github
3 | .gitignore
4 | .npmrc
5 |
--------------------------------------------------------------------------------
/packages/web-tools/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/.npmrc:
--------------------------------------------------------------------------------
1 |
2 | registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const PRESET_NAME = "npm-pkg";
2 |
--------------------------------------------------------------------------------
/packages/web-tools/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const PRESET_NAME = "web-tools";
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
3 | - "examples/*"
4 | - "website"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/packages/create-doctor/bin/create-doctor.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require("../dist/cli.js");
4 |
--------------------------------------------------------------------------------
/examples/npm-pkg/src/cjsModule.ts:
--------------------------------------------------------------------------------
1 | // 不报错
2 | const lodash = require("lodash");
3 |
4 | console.log(lodash);
5 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/constants.ts.tpl:
--------------------------------------------------------------------------------
1 | export const PRESET_NAME = "{{{ command }}}";
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/constants.ts.tpl:
--------------------------------------------------------------------------------
1 | export const PRESET_NAME = "{{{ command }}}";
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Config} */
2 | module.exports = {
3 | singleQuote: false,
4 | };
5 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/features/index.ts.tpl:
--------------------------------------------------------------------------------
1 | export default [require.resolve("./check")];
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/features/index.ts.tpl:
--------------------------------------------------------------------------------
1 | export default [require.resolve("./check")];
2 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .dumi
5 | .pnpm-debug.log
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .dumi
5 | .pnpm-debug.log
--------------------------------------------------------------------------------
/examples/npm-pkg/src/index.ts:
--------------------------------------------------------------------------------
1 | const cjsImportEsm = require("./cjsImportEsm");
2 |
3 | console.log(cjsImportEsm.filter);
4 |
--------------------------------------------------------------------------------
/packages/utils/src/features/getIntNodeVersion.ts:
--------------------------------------------------------------------------------
1 | export default function () {
2 | return parseInt(process.version.slice(1));
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .idea
5 | .vscode
6 | .dumi
7 | server
8 | .pnpm-debug.log
9 | pnpm-lock.yaml
10 |
--------------------------------------------------------------------------------
/examples/npm-pkg/src/cjsImportEsm.ts:
--------------------------------------------------------------------------------
1 | // 报错
2 | import lodash from "lodash-es";
3 |
4 | module.exports = {
5 | filter: lodash.filter,
6 | };
7 |
--------------------------------------------------------------------------------
/examples/npm-pkg/src/awaitImport.ts:
--------------------------------------------------------------------------------
1 | // 不报错
2 | async function test() {
3 | const res = await import("lodash-es");
4 | console.log(res);
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/packages/diy/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/packages/npm-pkg/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/packages/utils/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/packages/web-tools/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/website/docs/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: Doctors - For Web Development
4 | ---
5 |
6 |
7 |
--------------------------------------------------------------------------------
/website/docs/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: Doctors - For Web Development
4 | ---
5 |
6 |
7 |
--------------------------------------------------------------------------------
/packages/create-doctor/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | extends: "../../.fatherrc.base",
5 | });
6 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/defineConfig.ts:
--------------------------------------------------------------------------------
1 | import { ConfigSchema } from "./type";
2 |
3 | export default function defineConfig(config: ConfigSchema) {
4 | return config;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/web-tools/src/defineConfig.ts:
--------------------------------------------------------------------------------
1 | import { ConfigSchema } from "./type";
2 |
3 | export default function defineConfig(config: ConfigSchema) {
4 | return config;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/bin/doctor.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require("../dist/cli/cli")
4 | .run()
5 | .catch((e) => {
6 | console.error(e);
7 | process.exit(1);
8 | });
9 |
--------------------------------------------------------------------------------
/website/docs/changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 更新日志
3 | nav:
4 | title: 更新日志
5 | order: 999
6 | sidebar: false
7 | tocDepth: 2
8 | token:
9 | contentMaxWidth: 1280
10 | ---
11 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/defineConfig.ts.tpl:
--------------------------------------------------------------------------------
1 | import { ConfigSchema } from "./type";
2 |
3 | export default function defineConfig(config: ConfigSchema) {
4 | return config;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/utils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./features";
2 | export * from "./ui";
3 | export * from "./types";
4 | export * from "./cache";
5 | export * from "./exec";
6 | export * from "./common";
7 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/defineConfig.ts.tpl:
--------------------------------------------------------------------------------
1 | import { ConfigSchema } from "./type";
2 |
3 | export default function defineConfig(config: ConfigSchema) {
4 | return config;
5 | }
6 |
--------------------------------------------------------------------------------
/.fatherrc.base.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | cjs: {
5 | output: "./dist",
6 | },
7 | prebundle: {
8 | deps: {},
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/packages/utils/src/features/getDefaultBrowser.ts:
--------------------------------------------------------------------------------
1 | export default async function getDefaultBrowser() {
2 | const defaultBrowser = await import("default-browser");
3 | return (await defaultBrowser.default()).name;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types";
2 | export * from "./utils";
3 | export * from "./config";
4 | export { default as generatePreset } from "./generatePreset";
5 | export { default as generateDiy } from "./generateDiy";
6 |
--------------------------------------------------------------------------------
/examples/web-tools/.doctor.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { defineConfig } from "@doctors/web-tools";
3 |
4 | export default defineConfig({
5 | webTools: {
6 | nodeVersion: DoctorLevel.ERROR,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | cjs: {
5 | output: "./dist",
6 | },
7 | prebundle: {
8 | deps: {},
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "father";
2 |
3 | export default defineConfig({
4 | cjs: {
5 | output: "./dist",
6 | },
7 | prebundle: {
8 | deps: {},
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/features/index.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | require.resolve("./depInPeerDependencies"),
3 | require.resolve("./preferPackFiles"),
4 | require.resolve("./checkPkgFilesExist"),
5 | require.resolve("./cjsImportEsm"),
6 | ];
7 |
--------------------------------------------------------------------------------
/packages/web-tools/src/features/index.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | require.resolve("./checkNode"),
3 | require.resolve("./checkChrome"),
4 | require.resolve("./checkDefaultBrowser"),
5 | require.resolve("./checkGitSshKeyPersistent"),
6 | ];
7 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import { alias } from "./alias";
3 |
4 | export default defineConfig({
5 | optimizeDeps: {
6 | entries: [],
7 | },
8 | resolve: {
9 | alias,
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/packages/core/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const FRAMEWORK_NAME = "doctor";
2 | export const DEFAULT_CONFIG_FILES = [".doctor.ts", ".doctor.js"];
3 |
4 | export const DEV_COMMAND = "dev";
5 | export const BUILD_COMMANDS = "dev";
6 |
7 | export const MIN_NODE_VERSION = 14;
8 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": true,
5 | "skipLibCheck": true,
6 | "baseUrl": "./",
7 | "moduleDetection": "auto",
8 | "esModuleInterop": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": true,
5 | "skipLibCheck": true,
6 | "baseUrl": "./",
7 | "moduleDetection": "auto",
8 | "esModuleInterop": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/utils/src/features/index.ts:
--------------------------------------------------------------------------------
1 | export { default as getIntNodeVersion } from "./getIntNodeVersion";
2 | export { default as isChromeInstalled } from "./isChromeInstalled";
3 | export { default as getDefaultBrowser } from "./getDefaultBrowser";
4 | export { default as IsGitPersistent } from "./IsGitPersistent";
5 |
--------------------------------------------------------------------------------
/packages/utils/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { type IDoctorSourceParseResult } from "../features/scanningParser";
2 |
3 | export type Awaitable = T | Promise;
4 |
5 | export interface SourceFile {
6 | path?: string;
7 | imports?: IDoctorSourceParseResult["imports"];
8 | contents?: string;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/create-doctor/README.md:
--------------------------------------------------------------------------------
1 | # 使用 脚手架 快速生成 doctor preset 模板
2 |
3 | > 暂未发布 npm
4 |
5 | ```sh
6 | npx create-doctor
7 | ```
8 |
9 | 选择 preset 或 feature,然后根据提示进行输入
10 |
11 | #### preset 能力体验:
12 |
13 | 创建好项目之后
14 |
15 | ```sh
16 | npm i
17 | npm run dev
18 | npm link // link 到全局
19 | doctor
20 | ```
21 |
--------------------------------------------------------------------------------
/packages/utils/src/features/IsGitPersistent.ts:
--------------------------------------------------------------------------------
1 | import { execCommand } from "../exec";
2 |
3 | /**
4 | * 如果此处有 error :没有正确配置上 ssh ,导致每次 git 操作都需要输入密码
5 | */
6 | export default async function IsGitPersistent() {
7 | try {
8 | await execCommand("ssh-add -L");
9 | return true;
10 | } catch (error) {
11 | return false;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/diy/.doctor.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { defineConfig } from "@doctors/npm-pkg";
3 |
4 | export default defineConfig({
5 | npmPkg: {
6 | peerDepAndDepRepeat: {
7 | level: DoctorLevel.WARN,
8 | exclude: ["@doctors/core"],
9 | },
10 | checkPkgFilesExist: {
11 | level: DoctorLevel.ERROR,
12 | },
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/packages/diy/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IApi, generateDiy } from "@doctors/core";
2 |
3 | export default (api: IApi) => {
4 | return generateDiy({
5 | api,
6 | presets: [
7 | require.resolve("@doctors/web-tools"),
8 | require.resolve("@doctors/npm-pkg"),
9 | require.resolve("doctors-yang-author"),
10 | ],
11 | commands: ["web-tools", "npm-pkg"],
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/alias.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from "node:path";
2 |
3 | function r(p: string) {
4 | return resolve(__dirname, p);
5 | }
6 |
7 | export const alias: Record = {
8 | "@doctors/core": r("./packages/core/src/"),
9 | "@doctors/webtools": r("./packages/web-tools/src/"),
10 | "@doctors/npmpkg": r("./packages/npm-pkg/src/"),
11 | "@doctors/utils": r("./packages/utils/src/"),
12 | };
13 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "start": "dumi dev",
5 | "site:build": "dumi build",
6 | "build:analyze": "ANALYZE=1 dumi build",
7 | "preview": "dumi preview",
8 | "setup": "dumi setup"
9 | },
10 | "devDependencies": {
11 | "dumi": "^2.2.1",
12 | "dumi-theme-antd-style": "^0.27.4"
13 | },
14 | "dependencies": {
15 | "@ant-design/icons": "^5.1.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/web-tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webtools-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "commonjs",
7 | "private": true,
8 | "scripts": {
9 | "doctor:webtools": "doctor web-tools",
10 | "test": "npm run doctor:webtools"
11 | },
12 | "dependencies": {
13 | "@doctors/web-tools": "workspace:^"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "ISC"
18 | }
19 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/features/check.ts.tpl:
--------------------------------------------------------------------------------
1 | import type { IApi } from "../type";
2 | import { DoctorLevel } from "@doctors/core";
3 |
4 | export default (api: IApi) => {
5 | api.addDoctorCheckBefore(() => {});
6 |
7 | api.addDoctorCheck(() => {
8 | return {
9 | label: "Happy Path",
10 | description: "",
11 | doctorLevel: DoctorLevel.SUCCESS,
12 | };
13 | });
14 |
15 | api.addDoctorCheckEnd(() => {});
16 | };
17 |
--------------------------------------------------------------------------------
/packages/utils/src/exec.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process";
2 |
3 | export function execCommand(command: string): Promise {
4 | return new Promise((resolve, reject) => {
5 | const child = exec(command, (error, stdout, stderr) => {
6 | if (error) {
7 | reject(error);
8 | } else {
9 | resolve(stdout.trim());
10 | }
11 | });
12 |
13 | child.on("close", () => {
14 | child.kill();
15 | });
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/examples/user-preset/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "user-preset",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "doctor:web-tools": "doctor web-tools",
9 | "test": "npm run doctor:web-tools"
10 | },
11 | "dependencies": {
12 | "@doctors/web-tools": "workspace:^",
13 | "doctors-yang-author": "^0.0.5"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "ISC"
18 | }
19 |
--------------------------------------------------------------------------------
/packages/utils/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const CACHE_PATH = "node_modules/.cache/father";
2 | export const DEFAULT_BUNDLESS_IGNORES = [
3 | "**/.*",
4 | "**/.*/**",
5 | "**/*.md",
6 | "**/demos/**",
7 | "**/fixtures/**",
8 | "**/__{test,tests,snapshots}__/**",
9 | "**/*.{test,e2e,spec}.{js,jsx,ts,tsx}",
10 | "**/tsconfig.json",
11 | ];
12 | export const DEFAULT_SOURCE_IGNORES = [
13 | "**/.*",
14 | "**/.*/**",
15 | "**/node_modules/**",
16 | "**/dist/**",
17 | "**/build/**",
18 | ];
19 |
--------------------------------------------------------------------------------
/scripts/preinstall.js:
--------------------------------------------------------------------------------
1 | const childProcess = require("child_process");
2 |
3 | let version;
4 | try {
5 | version = childProcess.execSync("pnpm -v").toString();
6 | } catch (err) {
7 | console.error(`Error: can't find module 'pnpm'.`);
8 | process.exit(1);
9 | }
10 |
11 | const majorVersion = version?.split(".")?.[0];
12 | if (!majorVersion || +majorVersion < 8) {
13 | console.error(
14 | `Error: required pnpm version is not less than 8.0.0, but got ${version}.`
15 | );
16 | process.exit(1);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/commands/command.ts.tpl:
--------------------------------------------------------------------------------
1 | import { IApi, generatePreset } from "@doctors/core";
2 | import { Nullify } from "@doctors/core";
3 | import { ConfigSchema } from "../type";
4 | import { PRESET_NAME } from "../constants";
5 |
6 | const schema: Nullify = {};
7 |
8 | // meta 元数据 将会作为所有 feature 插件的实参传入 供使用
9 | const meta = {};
10 |
11 | export default (api: IApi) => {
12 | generatePreset({
13 | api,
14 | command: PRESET_NAME,
15 | schema,
16 | meta,
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/features/check.ts.tpl:
--------------------------------------------------------------------------------
1 | import type { IApi } from "../type";
2 | import { DoctorLevel } from "@doctors/core";
3 |
4 | export default (api: IApi) => {
5 | api.addDoctor{{{ commandCamelCased }}}CheckBefore(() => {});
6 |
7 | api.addDoctor{{{ commandCamelCased }}}Check(() => {
8 | return {
9 | label: "Happy Path",
10 | description: "",
11 | doctorLevel: DoctorLevel.SUCCESS,
12 | };
13 | });
14 |
15 | api.addDoctor{{{ commandCamelCased }}}CheckEnd(() => {});
16 | };
17 |
--------------------------------------------------------------------------------
/packages/utils/src/cache.ts:
--------------------------------------------------------------------------------
1 | import { CACHE_PATH } from "./constants";
2 | import Cache from "file-system-cache";
3 | import path from "path";
4 |
5 | const caches: Record> = {};
6 |
7 | export function getCache(ns: string): (typeof caches)["0"] {
8 | // return fake cache if cache disabled
9 | if (process.env.FATHER_CACHE === "none") {
10 | return { set() {}, get() {}, setSync() {}, getSync() {} } as any;
11 | }
12 | return (caches[ns] ??= Cache({ basePath: path.join(CACHE_PATH, ns) }));
13 | }
14 |
--------------------------------------------------------------------------------
/test/npm-pkg.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "vitest";
2 | import { ConfigSchema, defineConfig } from "@doctors/npm-pkg";
3 | import { DoctorLevel } from "@doctors/core";
4 |
5 | test("define config should return raw config", async () => {
6 | const rawConfig: ConfigSchema = {
7 | npmPkg: {
8 | peerDepAndDepRepeat: {
9 | level: DoctorLevel.WARN,
10 | exclude: ["@doctors/core"],
11 | },
12 | },
13 | };
14 |
15 | const config = defineConfig(rawConfig);
16 |
17 | expect(config).equal(rawConfig);
18 | });
19 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/index.ts.tpl:
--------------------------------------------------------------------------------
1 | import features from "./features";
2 | import { IApi } from "./type";
3 | import { PRESET_NAME } from "./constants";
4 |
5 | // 为用户导出需要的 类型 和 工具函数
6 | export * from "./type";
7 | export { default as defineConfig } from "./defineConfig";
8 |
9 | export default (api: IApi) => {
10 | // key 用来识别插件 否则会使用默认的小驼峰包名 容易和其他插件重复
11 | api.describe({
12 | key: `doctor-preset-${PRESET_NAME}`,
13 | });
14 |
15 | return {
16 | plugins: [
17 | //features
18 | ...features,
19 | ],
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/packages/web-tools/src/features/checkChrome.ts:
--------------------------------------------------------------------------------
1 | import { IApi } from "../type";
2 | import { DoctorLevel } from "@doctors/core";
3 | import { isChromeInstalled } from "@doctors/utils";
4 |
5 | export default (api: IApi) => {
6 | api.addDoctorWebToolsCheck(async () => {
7 | const isInstalled = await isChromeInstalled();
8 |
9 | if (isInstalled) {
10 | return {
11 | label: "isChromeInstalled",
12 | description: "You should apply Chrome for web development",
13 | doctorLevel: DoctorLevel.SUCCESS,
14 | };
15 | }
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/packages/web-tools/src/commands/web-tools.ts:
--------------------------------------------------------------------------------
1 | import { IApi, generatePreset } from "@doctors/core";
2 | import { Nullify } from "@doctors/core";
3 | import { ConfigSchema } from "../type";
4 | import { PRESET_NAME } from "../constants";
5 |
6 | const schema: Nullify = {
7 | webTools: {
8 | nodeVersion: null,
9 | gitSshKey: null,
10 | },
11 | };
12 |
13 | // meta 元数据 将会作为所有 feature 插件的实参传入 供使用
14 | const meta = {};
15 |
16 | export default (api: IApi) => {
17 | generatePreset({
18 | api,
19 | command: PRESET_NAME,
20 | schema,
21 | meta,
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/packages/create-doctor/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { prompts } from "@umijs/utils";
2 |
3 | export function isKebabCase(str: string) {
4 | return /^[a-z]+(-[a-z]+)*$/.test(str);
5 | }
6 |
7 | export function kebabToCamelCase(kebabCase: string) {
8 | return kebabCase
9 | .split("-")
10 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
11 | .join("");
12 | }
13 |
14 | export function promptsWithCancel(
15 | questions: prompts.PromptObject | prompts.PromptObject[]
16 | ) {
17 | return prompts(questions, {
18 | onCancel() {
19 | process.exit(0);
20 | },
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/packages/web-tools/src/index.ts:
--------------------------------------------------------------------------------
1 | import features from "./features";
2 | import { IApi } from "./type";
3 | import { PRESET_NAME } from "./constants";
4 |
5 | // 为用户导出需要的 类型 和 工具函数
6 | export * from "./type";
7 | export { default as defineConfig } from "./defineConfig";
8 |
9 | export default (api: IApi) => {
10 | // key 用来识别插件 否则会使用默认的小驼峰包名 容易和其他插件重复
11 | api.describe({
12 | key: `doctor-preset-${PRESET_NAME}`,
13 | });
14 |
15 | return {
16 | plugins: [
17 | //commands
18 | require.resolve("./commands/web-tools"),
19 | //features
20 | ...features,
21 | ],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/examples/diy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm-pkg-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "doctor:diy": "doctor diy",
9 | "test": "npm run doctor:diy"
10 | },
11 | "files": [
12 | "dist",
13 | "./bin1",
14 | "./noExist"
15 | ],
16 | "dependencies": {
17 | "@doctors/diy": "workspace:^"
18 | },
19 | "peerDependencies": {
20 | "@doctors/diy": "workspace:^"
21 | },
22 | "keywords": [],
23 | "author": "",
24 | "license": "ISC",
25 | "devDependencies": {
26 | "shelljs": "^0.8.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/index.ts.tpl:
--------------------------------------------------------------------------------
1 | import features from "./features";
2 | import { IApi } from "./type";
3 | import { PRESET_NAME } from "./constants";
4 |
5 | // 为用户导出需要的 类型 和 工具函数
6 | export * from "./type";
7 | export { default as defineConfig } from "./defineConfig";
8 |
9 | export default (api: IApi) => {
10 | // key 用来识别插件 否则会使用默认的小驼峰包名 容易和其他插件重复
11 | api.describe({
12 | key: `doctor-preset-${PRESET_NAME}`,
13 | });
14 |
15 | return {
16 | plugins: [
17 | //commands
18 | require.resolve("./commands/{{{ commandFile }}}"),
19 | //features
20 | ...features,
21 | ],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/.github/workflows/issue-label.yaml:
--------------------------------------------------------------------------------
1 | name: Issue Labeled
2 |
3 | # 新增标签时触发
4 | on:
5 | issues:
6 | types: [labeled]
7 |
8 | jobs:
9 | issue-labeled:
10 | # 打标签和提交 issue 的不是同一个人才执行
11 | if: github.actor != github.event.issue.user.login
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Need more info
15 | if: github.event.label.name == 'TODO'
16 | uses: actions-cool/issues-helper@main
17 | with:
18 | actions: "create-comment"
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 | issue-number: ${{ github.event.issue.number }}
21 | body: |
22 | Something is being better!🥰
23 |
--------------------------------------------------------------------------------
/examples/npm-pkg/.doctor.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { defineConfig } from "@doctors/npm-pkg";
3 |
4 | export default defineConfig({
5 | npmPkg: {
6 | peerDepAndDepRepeat: {
7 | level: DoctorLevel.WARN,
8 | exclude: ["@doctors/core"],
9 | },
10 | checkPkgFilesExist: {
11 | level: DoctorLevel.ERROR,
12 | },
13 | preferPackFiles: {
14 | level: DoctorLevel.WARN,
15 | },
16 | compileFiles: ["src/index.ts"],
17 | // 开启cjs选项才会进行cjs相关检查,根据 compileFiles 的文件为扫描该文件路径下的所有js和ts文件,没有 compileFiles 字段则对项目全部的ts和js文件进行判断
18 | cjs: {
19 | open: true,
20 | cjsImportEsm: {
21 | level: DoctorLevel.WARN,
22 | },
23 | },
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/packages/create-doctor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-doctor",
3 | "version": "0.0.37",
4 | "description": "the create CLI tool for doctor",
5 | "main": "index.js",
6 | "bin": {
7 | "create-doctor": "./bin/create-doctor.js"
8 | },
9 | "scripts": {
10 | "dev": "father dev",
11 | "build": "father build",
12 | "build:deps": "father prebundle",
13 | "prepublishOnly": "father doctor && npm run build"
14 | },
15 | "repository": "https://github.com/FE-Struggler/doctor",
16 | "keywords": [],
17 | "authors": [
18 | "yixiaojiu"
19 | ],
20 | "license": "MIT",
21 | "files": [
22 | "./dist",
23 | "templates"
24 | ],
25 | "dependencies": {
26 | "@umijs/utils": "^4.0.71"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/create-doctor/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { chalk, isLocalDev, yParser } from "@umijs/utils";
2 |
3 | const args = yParser(process.argv.slice(2), {
4 | alias: {
5 | version: ["v"],
6 | help: ["h"],
7 | },
8 | boolean: ["version"],
9 | });
10 |
11 | if (args.version && !args._[0]) {
12 | args._[0] = "version";
13 | const local = isLocalDev() ? chalk.cyan("@local") : "";
14 | const { name, version } = require("../package.json");
15 | console.log(`${name} ${version}${local}`);
16 | } else {
17 | require("./")
18 | .default({
19 | cwd: process.cwd(),
20 | args,
21 | })
22 | .catch((err: Error) => {
23 | console.error(`Create failed, ${err.message}`);
24 | console.error(err);
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/src/type.ts.tpl:
--------------------------------------------------------------------------------
1 | import type { IApi as DoctorApi, DoctorMeta, DoctorLevel } from "@doctors/core";
2 |
3 | // 校验配置文件以及类型提示的 Schema 列表
4 | /**
5 | * 用每一个 Preset 来单独维护 Schema 以便获得更全面的类型提示
6 | */
7 | export interface ConfigSchema {
8 | }
9 |
10 | // 元数据
11 | interface Meta {}
12 |
13 | export type IApi = DoctorApi & {
14 | addDoctor{{{ commandCamelCased }}}CheckBefore: (fn: () => void) => void;
15 |
16 | addDoctor{{{ commandCamelCased }}}Check: {
17 | (fn: (meta?: Meta) => DoctorMeta | undefined): void;
18 | (fn: (meta?: Meta) => Promise): void;
19 | };
20 |
21 | addDoctor{{{ commandCamelCased }}}CheckEnd: (fn: () => void) => void;
22 | } & {
23 | userConfig: ConfigSchema;
24 | };
25 |
--------------------------------------------------------------------------------
/examples/npm-pkg/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm-pkg-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "doctor:npm-pkg": "doctor npm-pkg",
9 | "test": "npm run doctor:npm-pkg"
10 | },
11 | "files": [
12 | "dist1",
13 | "./bin1",
14 | "./noExist",
15 | "dist2/*.js",
16 | "bin2/*.js"
17 | ],
18 | "dependencies": {
19 | "@doctors/core": "workspace:^",
20 | "@doctors/npm-pkg": "workspace:^",
21 | "lodash": "^4.17.21",
22 | "lodash-es": "^4.17.21"
23 | },
24 | "peerDependencies": {
25 | "@doctors/core": "workspace:^",
26 | "@doctors/npm-pkg": "workspace:^"
27 | },
28 | "keywords": [],
29 | "author": "",
30 | "license": "ISC"
31 | }
32 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/src/type.ts.tpl:
--------------------------------------------------------------------------------
1 | import type { IApi as DoctorApi, DoctorMeta, DoctorLevel } from "@doctors/core";
2 |
3 | // 校验配置文件以及类型提示的 Schema 列表
4 | /**
5 | * 用每一个 Preset 来单独维护 Schema 以便获得更全面的类型提示
6 | */
7 | export interface ConfigSchema {
8 | webTools: {
9 | nodeVersion: DoctorLevel;
10 | };
11 | }
12 |
13 | // 元数据
14 | interface Meta {}
15 |
16 | export type IApi = DoctorApi & {
17 | addDoctor{{{ commandCamelCased }}}CheckBefore: (fn: () => void) => void;
18 |
19 | addDoctor{{{ commandCamelCased }}}Check: {
20 | (fn: (meta?: Meta) => DoctorMeta | undefined): void;
21 | (fn: (meta?: Meta) => Promise): void;
22 | };
23 |
24 | addDoctor{{{ commandCamelCased }}}CheckEnd: (fn: () => void) => void;
25 | };
26 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/index.ts:
--------------------------------------------------------------------------------
1 | import features from "./features";
2 | import { IApi } from "./type";
3 | import { PRESET_NAME } from "./constants";
4 |
5 | export * from "./type";
6 | export { default as defineConfig } from "./defineConfig";
7 | export { default as dupInPeerDependencies } from "./features/depInPeerDependencies";
8 | export { default as checkPkgFilesExist } from "./features/checkPkgFilesExist";
9 | export { default as preferPackFiles } from "./features/preferPackFiles";
10 | export { default as cjsImportEsm } from "./features/cjsImportEsm";
11 |
12 | export default (api: IApi) => {
13 | api.describe({
14 | key: `doctor-preset-${PRESET_NAME}`,
15 | });
16 |
17 | return {
18 | plugins: [require.resolve("./commands/npm-pkg"), ...features],
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/features/preferPackFiles.ts:
--------------------------------------------------------------------------------
1 | import type { ConfigSchema, IApi } from "../type";
2 | import { DoctorLevel, DoctorMeta } from "@doctors/core";
3 |
4 | export default function depInPeerDependencies(api: IApi) {
5 | const userConfig = api.userConfig as ConfigSchema;
6 | api.addDoctorNpmPkgCheck(() => {
7 | if (!api.pkg.files) {
8 | return {
9 | label: "preferPackFiles",
10 | description:
11 | "No `files` field in the package.json file, all the non-gitignore files will be published",
12 | doctorLevel:
13 | userConfig.npmPkg?.preferPackFiles?.level || DoctorLevel.WARN,
14 | };
15 | }
16 |
17 | return {
18 | label: "preferPackFiles",
19 | description: "`files` field is in the package.json file",
20 | doctorLevel: DoctorLevel.SUCCESS,
21 | };
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { IServicePluginAPI, PluginAPI } from "@umijs/core";
2 |
3 | export type IApi = PluginAPI & IServicePluginAPI;
4 |
5 | export interface PluginMeta {
6 | name: string;
7 | version?: string;
8 | path: string;
9 | hasCommand: boolean;
10 | }
11 |
12 | export interface DoctorMeta {
13 | label: string;
14 | description: string;
15 | doctorLevel: DoctorLevel;
16 | }
17 |
18 | export enum DoctorLevel {
19 | OFF = "off",
20 | WARN = "warn",
21 | ERROR = "error",
22 | SUCCESS = "success",
23 | }
24 |
25 | export type Nullify = {
26 | [K in keyof T]: {
27 | [P in keyof T[K]]: null;
28 | };
29 | };
30 |
31 | export interface RuleResItem {
32 | descriptions: {
33 | level: DoctorLevel;
34 | suggestion: string;
35 | }[];
36 | label: string;
37 | description: string;
38 | doctorLevel: DoctorLevel;
39 | }
40 |
--------------------------------------------------------------------------------
/packages/web-tools/src/features/checkNode.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { IApi } from "../type";
3 | import { chalkByDoctorLevel, getIntNodeVersion } from "@doctors/utils";
4 |
5 | export default (api: IApi) => {
6 | api.addDoctorWebToolsCheck(() => {
7 | const ruleLevel = api.userConfig.webTools?.nodeVersion || DoctorLevel.WARN;
8 |
9 | if (ruleLevel === DoctorLevel.OFF) return;
10 |
11 | const nodeVersion = getIntNodeVersion();
12 | const isNodeVersionFine = nodeVersion > 12;
13 | const doctorLevel = isNodeVersionFine ? DoctorLevel.SUCCESS : ruleLevel;
14 |
15 | return {
16 | label: "Node Version",
17 | description: `Node Version should be greater than 12.x ${chalkByDoctorLevel(
18 | doctorLevel,
19 | `Now: ${process.version}`
20 | )}`,
21 | doctorLevel,
22 | };
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/packages/web-tools/src/type.ts:
--------------------------------------------------------------------------------
1 | import type { IApi as DoctorApi, DoctorMeta, DoctorLevel } from "@doctors/core";
2 | import type { Awaitable } from "@doctors/utils";
3 |
4 | /**
5 | * 用每一个 Preset 来单独维护 Schema 以便获得更全面的类型提示
6 | */
7 | export interface ConfigSchema {
8 | webTools: {
9 | nodeVersion: DoctorLevel;
10 | gitSshKey: DoctorLevel;
11 | };
12 | }
13 |
14 | /** 元数据 **/
15 | interface Meta {}
16 |
17 | export interface DoctorLifeCycleApi {
18 | addDoctorWebToolsCheckBefore: (fn: () => Awaitable) => void;
19 |
20 | addDoctorWebToolsCheck: {
21 | (fn: (meta?: Meta) => DoctorMeta | undefined): void;
22 | (fn: (meta?: Meta) => Awaitable): void;
23 | };
24 |
25 | addDoctorWebToolsCheckEnd: (fn: () => Awaitable) => void;
26 | }
27 |
28 | export type IApi = DoctorApi &
29 | DoctorLifeCycleApi & {
30 | userConfig: ConfigSchema;
31 | };
32 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | pull_request:
9 | branches:
10 | - master
11 |
12 | concurrency:
13 | group: ${{github.workflow}}-${{github.event_name}}-${{github.ref}}
14 | cancel-in-progress: true
15 |
16 | jobs:
17 | ci:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: pnpm/action-setup@v2
22 | with:
23 | version: 8.0
24 |
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v3
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 |
30 | - name: Install
31 | run: pnpm install --no-frozen-lockfile
32 |
33 | - name: Bundle for all packages
34 | run: npm run build:all
35 |
36 | - name: Typecheck
37 | run: npm run typecheck
38 |
39 |
--------------------------------------------------------------------------------
/packages/web-tools/src/features/checkDefaultBrowser.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { IApi } from "../type";
3 | import { chalkByDoctorLevel, getDefaultBrowser } from "@doctors/utils";
4 |
5 | export default (api: IApi) => {
6 | api.addDoctorWebToolsCheck(async () => {
7 | const defaultBrowserName = await getDefaultBrowser();
8 | const description = `Set the Chrome as default browser is best ${chalkByDoctorLevel(
9 | DoctorLevel.SUCCESS,
10 | `Now: ${defaultBrowserName}`
11 | )}`;
12 | if (defaultBrowserName === "Chrome") {
13 | return {
14 | label: "Default Browser",
15 | description,
16 | doctorLevel: DoctorLevel.SUCCESS,
17 | };
18 | } else {
19 | return {
20 | label: "Default Browser",
21 | description,
22 | doctorLevel: DoctorLevel.WARN,
23 | };
24 | }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/packages/web-tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@doctors/web-tools",
3 | "version": "0.0.37",
4 | "description": "",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "check": "father doctor",
12 | "deploy": "pnpm publish",
13 | "prepublishOnly": "father doctor && npm run build"
14 | },
15 | "repository": "https://github.com/FE-Struggler/doctor",
16 | "keywords": [],
17 | "authors": [
18 | "洋"
19 | ],
20 | "license": "MIT",
21 | "files": [
22 | "./dist",
23 | "./bin"
24 | ],
25 | "publishConfig": {
26 | "access": "public"
27 | },
28 | "dependencies": {
29 | "@doctors/core": "workspace:^",
30 | "@doctors/utils": "workspace:^",
31 | "@umijs/core": "^4.0.71",
32 | "@umijs/utils": "^4.0.71"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - 'v*'
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - uses: pnpm/action-setup@v2
17 | with:
18 | version: 8.0
19 | fetch-depth: 0
20 |
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 |
26 | - run: npx changelogithub
27 | env:
28 | # 配置 GITHUB_TOKEN
29 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
30 | - name: Publish to NPM
31 | run: pnpm -r publish --access public --no-git-checks
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
34 | NPM_CONFIG_PROVENANCE: true
35 |
36 |
--------------------------------------------------------------------------------
/packages/diy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@doctors/diy",
3 | "version": "0.0.37",
4 | "description": "",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "check": "father doctor",
12 | "deploy": "pnpm publish",
13 | "prepublishOnly": "father doctor && npm run build"
14 | },
15 | "repository": "https://github.com/FE-Struggler/doctor",
16 | "keywords": [],
17 | "authors": [
18 | "洋"
19 | ],
20 | "license": "MIT",
21 | "files": [
22 | "./dist",
23 | "./bin"
24 | ],
25 | "publishConfig": {
26 | "access": "public"
27 | },
28 | "dependencies": {
29 | "@doctors/core": "workspace:^",
30 | "@doctors/npm-pkg": "workspace:^",
31 | "@doctors/web-tools": "workspace:^",
32 | "doctors-yang-author": "^0.0.6"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/web-tools/src/features/checkGitSSHKeyPersistent.ts:
--------------------------------------------------------------------------------
1 | import { DoctorLevel } from "@doctors/core";
2 | import { IApi } from "../type";
3 | import { IsGitPersistent } from "@doctors/utils";
4 |
5 | export default (api: IApi) => {
6 | api.addDoctorWebToolsCheck(async () => {
7 | const isGitPersist = await IsGitPersistent();
8 |
9 | // 配置默认规则
10 | const ruleLevel = api.userConfig.webTools.gitSshKey || DoctorLevel.WARN;
11 |
12 | if (isGitPersist) {
13 | return {
14 | label: "isGitSshKeyPersistent",
15 | description: `Git ssh has been persistent config`,
16 | doctorLevel: DoctorLevel.SUCCESS,
17 | };
18 | } else {
19 | return {
20 | label: "isGitSshKeyPersistent",
21 | description: `Git ssh has not been persistent configured yet. Please open your terminal and enter ssh-add`,
22 | doctorLevel: ruleLevel,
23 | };
24 | }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/website/docs/config/web-tools.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Web 研发工具
3 | order: 1
4 | nav:
5 | title: 文档
6 | order: 2
7 | ---
8 |
9 | # `@doctors/web-tools`
10 |
11 | 为了更好地满足自定义诉求,增加了一些特有字段,具体配置字段如下:
12 |
13 | ## Config Schema 💻
14 |
15 | ```ts
16 | interface Schema {
17 | webTools: {
18 | nodeVersion: DoctorLevel;
19 | gitSshKey: DoctorLevel;
20 | };
21 | }
22 | ```
23 |
24 | ### DoctorLevel
25 |
26 | | Level | 类型 | 描述 |
27 | | ----- | ----------------- | --------------------------- |
28 | | off | DoctorLevel.OFF | 关闭此条 doctor rule 的检测 |
29 | | warn | DoctorLevel.WARN | WARN 等级,不会打断后续进程 |
30 | | error | DoctorLevel.ERROR | Error 等级 打断后续进程 |
31 |
32 | ## 基础配置
33 |
34 | ### nodeVersion
35 |
36 | - 类型:DoctorLevel
37 | - 默认值:DoctorLevel.WARN
38 |
39 | 检测当前 Node 版本
40 |
41 | ### gitSshKey
42 |
43 | - 类型:DoctorLevel
44 | - 默认值:DoctorLevel.WARN
45 |
46 | 检测 gitSshKey 是否正确配置
47 |
--------------------------------------------------------------------------------
/website/docs/guide/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | nav:
4 | title: Guide
5 | order: 1
6 | group:
7 | title: Introduction
8 | order: -1
9 | ---
10 |
11 | # What is this?
12 |
13 | In the development process of [**Ant Design Style**](https://github.com/ant-design/antd-style), in order to test and validate the capabilities of custom themes, dynamic theme algorithms, etc., many stylized styles were tried on the basis of antd basic components, such as gradients, frosted glass blurs, etc. As many people expressed their love for this design, it was extracted into a separate [**dumi2**](https://github.com/umijs/dumi) theme package for reuse.
14 |
15 | ## Features
16 |
17 |
18 |
19 | ## Feedback
20 |
21 | If you encounter any problems during use or have suggestions for improvement, please feel free to provide feedback on [**GitHub Issues**](https://github.com/arvinxx/dumi-theme-antd-style/issues).
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2018",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "strictNullChecks": true,
9 | "resolveJsonModule": true,
10 | "skipDefaultLibCheck": true,
11 | "skipLibCheck": true,
12 | "declaration": true,
13 | "baseUrl": "./",
14 | "moduleDetection": "auto",
15 | "esModuleInterop": true,
16 | "strictPropertyInitialization": true,
17 | "noImplicitAny": false,
18 | "jsx": "preserve",
19 | "allowJs": true,
20 | "checkJs": true,
21 | "paths": {
22 | "@doctors/core": ["./packages/core/src/"],
23 | "@doctors/webtools": ["./packages/web-tools/src/"],
24 | "@doctors/npm-pkg": ["./packages/npm-pkg/src/"],
25 | "@doctors/utils": ["./packages/utils/src/"]
26 | }
27 | },
28 | "exclude": ["**/dist/**", "**/node_modules/**", "**/examples/**"]
29 | }
30 |
--------------------------------------------------------------------------------
/packages/npm-pkg/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@doctors/npm-pkg",
3 | "version": "0.0.37",
4 | "description": "",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "check": "father doctor",
12 | "deploy": "pnpm publish",
13 | "prepublishOnly": "father doctor && npm run build"
14 | },
15 | "repository": "https://github.com/FE-Struggler/doctor",
16 | "keywords": [],
17 | "authors": [
18 | "洋"
19 | ],
20 | "license": "MIT",
21 | "files": [
22 | "./dist",
23 | "./bin"
24 | ],
25 | "publishConfig": {
26 | "access": "public"
27 | },
28 | "dependencies": {
29 | "@doctors/core": "workspace:^",
30 | "@umijs/core": "^4.0.71",
31 | "@umijs/utils": "^4.0.71",
32 | "enhanced-resolve": "^5.15.0",
33 | "glob": "^10.3.0",
34 | "vm": "^0.1.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/feature/package.json.tpl:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{{ packageName }}}",
3 | "version": "0.1.0",
4 | "description": "{{{ description }}}",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "prepublishOnly": "father doctor && npm run build"
12 | },
13 | "keywords": [],
14 | "author": "{{{ author }}}",
15 | "license": "MIT",
16 | "files": [
17 | "./dist",
18 | "./bin"
19 | ],
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "peerDependencies": {
24 | "@doctors/core": "latest"
25 | },
26 | "dependencies": {
27 | "@doctors/utils": "latest",
28 | "@umijs/core": "^4.0.71",
29 | "@umijs/utils": "^4.0.71"
30 | },
31 | "devDependencies": {
32 | "father": "^4.1.8",
33 | "@types/node": "^18.15.11",
34 | "typescript": "^5.0.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/create-doctor/templates/preset/package.json.tpl:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{{ packageName }}}",
3 | "version": "0.1.0",
4 | "description": "{{{ description }}}",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "prepublishOnly": "father doctor && npm run build"
12 | },
13 | "keywords": [],
14 | "author": "{{{ author }}}",
15 | "license": "MIT",
16 | "files": [
17 | "./dist",
18 | "./bin"
19 | ],
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "peerDependencies": {
24 | "@doctors/core": "latest"
25 | },
26 | "dependencies": {
27 | "@doctors/utils": "latest",
28 | "@umijs/core": "^4.0.71",
29 | "@umijs/utils": "^4.0.71"
30 | },
31 | "devDependencies": {
32 | "father": "^4.1.8",
33 | "@types/node": "^18.15.11",
34 | "typescript": "^5.0.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@doctors/core",
3 | "version": "0.0.37",
4 | "description": "",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "bin": {
8 | "doctor": "./bin/doctor.js"
9 | },
10 | "scripts": {
11 | "dev": "father dev",
12 | "build": "father build",
13 | "build:deps": "father prebundle",
14 | "check": "father doctor",
15 | "deploy": "pnpm publish",
16 | "prepublishOnly": "father doctor && npm run build"
17 | },
18 | "repository": "https://github.com/FE-Struggler/doctor",
19 | "keywords": [],
20 | "authors": [
21 | "洋"
22 | ],
23 | "license": "MIT",
24 | "files": [
25 | "./dist",
26 | "./bin"
27 | ],
28 | "publishConfig": {
29 | "access": "public"
30 | },
31 | "dependencies": {
32 | "@astrojs/cli-kit": "^0.2.3",
33 | "@umijs/core": "^4.0.71",
34 | "@umijs/utils": "^4.0.71",
35 | "umi": "^4.0.71",
36 | "v8-compile-cache": "2.3.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@doctors/utils",
3 | "version": "0.0.37",
4 | "description": "",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "dev": "father dev",
9 | "build": "father build",
10 | "build:deps": "father prebundle",
11 | "check": "father doctor",
12 | "deploy": "pnpm publish",
13 | "prepublishOnly": "father doctor && npm run build"
14 | },
15 | "repository": "https://github.com/FE-Struggler/doctor",
16 | "keywords": [],
17 | "authors": [
18 | "洋"
19 | ],
20 | "license": "MIT",
21 | "files": [
22 | "./dist",
23 | "./bin"
24 | ],
25 | "publishConfig": {
26 | "access": "public"
27 | },
28 | "dependencies": {
29 | "@umijs/core": "^4.0.71",
30 | "@umijs/utils": "^4.0.71",
31 | "esbuild": "^0.18.10",
32 | "file-system-cache": "^2.4.1",
33 | "glob": "^10.3.0",
34 | "lodash": "^4.17.21",
35 | "default-browser": "^4.0.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/utils/src/features/isChromeInstalled.ts:
--------------------------------------------------------------------------------
1 | import { execCommand } from "../exec";
2 | import os from "os";
3 |
4 | export const enum CheckChromeInstalledCommands {
5 | darwin = "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --version",
6 | win32 = 'reg query "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon" /v version',
7 | }
8 |
9 | function getCheckChromeCommandByOS() {
10 | if (os.platform() === "darwin") {
11 | return CheckChromeInstalledCommands.darwin;
12 | } else if (os.platform() === "win32") {
13 | return CheckChromeInstalledCommands.win32;
14 | }
15 | }
16 |
17 | const command = getCheckChromeCommandByOS();
18 |
19 | export default async function isChromeInstalled(): Promise {
20 | try {
21 | const stdout: string = await execCommand(command as string);
22 | return stdout.startsWith("Google Chrome") || stdout.includes("version");
23 | } catch (error) {
24 | console.error(error);
25 | return false;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/utils/src/ui.ts:
--------------------------------------------------------------------------------
1 | import { chalk } from "@umijs/utils";
2 | import { DoctorLevel } from "@doctors/core";
3 | import os from "os";
4 |
5 | export function chalkByDoctorLevel(doctorLevel: DoctorLevel, chalkStr: string) {
6 | switch (doctorLevel) {
7 | case DoctorLevel.SUCCESS:
8 | return chalk.green(chalkStr);
9 | case DoctorLevel.WARN:
10 | return chalk.yellowBright(chalkStr);
11 | case DoctorLevel.ERROR:
12 | return chalk.red(chalkStr);
13 | }
14 | }
15 |
16 | export const enum CheckChromeInstalledCommands {
17 | darwin = "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --version",
18 | win32 = 'reg query "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon" /v version',
19 | }
20 |
21 | export function getCheckChromeCommandByOS() {
22 | if (os.platform() === "darwin") {
23 | return CheckChromeInstalledCommands.darwin;
24 | } else if (os.platform() === "win32") {
25 | return CheckChromeInstalledCommands.win32;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/website/docs/guide/start-web-tools.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Web 研发工具篇
3 | order: 1
4 | nav:
5 | title: 指南
6 | order: 1
7 | group:
8 | title: 快速开始
9 | order: 2
10 | ---
11 |
12 | ## 适用场景
13 |
14 | `Web 研发工具` 是指用于支持 Web 应用程序开发的软件工具,包括但不限于 IDE(集成开发环境)、版本控制系统、构建工具、常用 Web 开发的 VsCode 插件、调试器和测试工具等。这些工具可以提高 Web 开发人员的生产力和效率,帮助他们更快速地开发高质量的 Web 应用程序。
15 |
16 | ## Why is Doctor ❓
17 |
18 | ### 对于 Web 开发的新手来说
19 |
20 | 研发工具的选择尤为重要。由于缺乏经验和知识,新手可能会花费大量时间和精力来解决一些简单的问题,而研发工具可以提供很多便利和自动化的功能,帮助他们更快速、更高效地完成任务。
21 | 此外,研发工具中也常常包含了许多学习资源和文档,这对于新手来说也是非常有帮助的。
22 |
23 | ### 对于 Web 开发的老手来说
24 |
25 | 研发工具同样重要。虽然老司机们已经有了丰富的开发经验和知识,但是随着 Web 技术的不断发展和更新,新的研发工具也在不断涌现,这些工具可以帮助他们更加高效地完成开发任务,提高代码质量和可维护性。此外,研发工具中也常常包含了最新的技术和最佳实践,这对于老司机们来说也是非常有益的。
26 |
27 | ## 快速开始
28 |
29 | ```sh
30 | npm i @doctors/web-tools@latest -g
31 | ```
32 |
33 | 🤔 why not npx:
34 | `npx` 相比 `npm @latest -g` 在缓存、版本更新确实强大,但因为 npx 缓存的缘故,为了保障 preset 组合能力,照顾 DX,这里是用性价比最高的方式
35 |
36 | ```sh
37 | doctor web-tools
38 | ```
39 |
40 | ## 配置文件 `.doctor.[ts|js]`
41 |
42 | [详细配置见](/config/web-tools)
43 |
--------------------------------------------------------------------------------
/packages/core/src/cli/node.ts:
--------------------------------------------------------------------------------
1 | import { logger } from "@umijs/utils";
2 | import { join } from "path";
3 | import { existsSync } from "fs";
4 | import { FRAMEWORK_NAME, MIN_NODE_VERSION } from "../constants";
5 |
6 | export function checkVersion() {
7 | const v = parseInt(process.version.slice(1));
8 | if (v < MIN_NODE_VERSION || v === 15 || v === 17) {
9 | logger.error(
10 | `Your node version ${v} is not supported, please upgrade to ${MIN_NODE_VERSION} or above except 15 or 17.`
11 | );
12 | process.exit(1);
13 | }
14 | }
15 |
16 | export function checkLocal() {
17 | if (existsSync(join(__dirname, "../../jest.config.ts"))) {
18 | logger.info("@local");
19 | }
20 | }
21 |
22 | export function setNodeTitle(name?: string) {
23 | if (process.title === "node") {
24 | process.title = name || FRAMEWORK_NAME;
25 | }
26 | }
27 |
28 | export function setNoDeprecation() {
29 | // Use magic to suppress node deprecation warnings
30 | // See: https://github.com/nodejs/node/blob/master/lib/internal/process/warning.js#L77
31 | // @ts-ignore
32 | process.noDeprecation = "1";
33 | }
34 |
--------------------------------------------------------------------------------
/website/docs/guide/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 简介
3 | nav:
4 | title: 指南
5 | order: 1
6 | group:
7 | title: 介绍
8 | order: 1
9 | ---
10 |
11 | # 这是什么?
12 |
13 | ### `Doctor` 是一套开源的 `Web 研发质量` 的工具链 🎉🎉
14 |
15 | - 基于 `@umijs/core` 封装出专属于 `Web doctor` 场景下的应用内核 `@doctors/core`
16 | - 高自由度、也可高度定制化、继承 `微内核` 可插拔能力、可拓展、可随意组合 local、global、npx cache 下的 doctor 插件
17 | - 覆盖 `Web 研发工具`、`npm 包开发质量检测`、`Web 应用开发最佳实践检测`、`构建工具质量检测`、`接入企业级应用框架` 等多种应用场景
18 | - 适用于 `个人开发者、中小型企业` 的 `通用开发场景` 质量检测预设、适用于 `复杂应用框架` 场景的定制需求
19 |
20 | ## 开源生态
21 |
22 | ```jsx
23 | /**
24 | * inline: true
25 | */
26 | import {
27 | DumiSiteProvider,
28 | Features,
29 | } from "../../node_modules/dumi-theme-antd-style";
30 | import { featuresZhForGuide } from "../../config/features";
31 |
32 | export default () => (
33 |
34 |
35 |
36 | );
37 | ```
38 |
39 | ## 开源共建 💗
40 |
41 | 欢迎各位开源爱好者~ 无论技术 无论年龄 只要你充满热情 享受开源 😄
👏 欢迎联系我 加入社区共建~
42 |
43 | 
44 |
--------------------------------------------------------------------------------
/packages/core/src/service/service.ts:
--------------------------------------------------------------------------------
1 | import { Service as CoreService } from "@umijs/core";
2 | import path from "path";
3 | import * as process from "process";
4 | import { DEFAULT_CONFIG_FILES, FRAMEWORK_NAME } from "../constants";
5 | import { getDoctorDependencies } from "../utils";
6 |
7 | export class Service extends CoreService {
8 | constructor(opts?: any) {
9 | let cwd = process.cwd();
10 | const appRoot = process.env.APP_ROOT;
11 |
12 | if (appRoot) {
13 | cwd = path.isAbsolute(appRoot) ? appRoot : path.join(cwd, appRoot);
14 | }
15 |
16 | super({
17 | ...opts,
18 | env: process.env.NODE_ENV,
19 | cwd,
20 | defaultConfigFiles: DEFAULT_CONFIG_FILES,
21 | frameworkName: FRAMEWORK_NAME,
22 | presets: [...getDoctorDependencies()],
23 | });
24 | }
25 |
26 | async run2(opts: { name: string; args?: any }) {
27 | let name = opts.name;
28 | if (opts?.args.version || name === "v") {
29 | name = "version";
30 | } else if (opts?.args.help || !name || name === "h") {
31 | name = "help";
32 | }
33 |
34 | return await this.run({ ...opts, name });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/website/docs/guide/feature.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Preset 组合
3 | order: 1
4 | nav:
5 | title: 指南
6 | group:
7 | title: 进阶特性
8 | order: 3
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/website/docs/guide/feature-feature.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Feature 拓展
3 | order: 3
4 | nav:
5 | title: 指南
6 | group:
7 | title: 进阶特性
8 | order: 3
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/website/docs/guide/feature-preset.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Preset 定制化
3 | order: 2
4 | nav:
5 | title: 指南
6 | group:
7 | title: 进阶特性
8 | order: 3
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/website/docs/guide/start-common.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 通用 Web 工程化项目
3 | order: 3
4 | nav:
5 | title: 指南
6 | group:
7 | title: 快速开始
8 | order: 2
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/website/docs/guide/start-complex-app.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 复杂应用研发场景
3 | order: 4
4 | nav:
5 | title: 指南
6 | group:
7 | title: 快速开始
8 | order: 2
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/website/docs/guide/start-npm-pkg.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: NPM 类库研发指南
3 | order: 2
4 | nav:
5 | title: 指南
6 | group:
7 | title: 快速开始
8 | order: 2
9 | ---
10 |
11 | ## 适用场景
12 |
13 | `NPM 类库研发` 是指开发和维护在 NPM(Node Package Manager)平台上发布的 JavaScript 类库。这些类库通常提供了一些常用的功能或工具函数,可以被其他开发者在他们的 Web 应用程序中使用。
14 |
15 | ## Why is Doctor ❓
16 |
17 | 从前端基础工具类库研发的全流程角度来看,对 NPM 类库研发进行检测的必要性非常重要,主要包括以下几个方面:
18 |
19 | - 配置检查:在进行 NPM 类库研发之前,需要对 npm 包的各种配置字段进行检查。例如,检查 package.json 文件中的最佳配置字段、入口文件、依赖关系等是否正确,以确保类库可以在其他应用程序中被正确安装和使用。
20 |
21 | - 构建打包检查:在进行 NPM 类库研发时,通常需要进行构建和打包操作,以生成可发布的代码。在这个过程中,需要对构建和打包过程进行检查,确保生成的代码可以被正确地加载和使用。
22 |
23 | - 发包检查:在进行 NPM 类库研发时,需要将生成的代码发布到 NPM 平台上,以供其他开发者使用。在这个过程中,需要对发布过程进行检查,确保发布的版本号、许可证和依赖关系等信息都是正确的。
24 |
25 | - 测试检查:在进行 NPM 类库研发时,需要编写和运行各种测试,以确保类库的功能和性能都是正确的。因此,需要对测试代码和测试结果进行检查,确保测试覆盖率足够高,测试结果正确可靠。
26 |
27 | ## 快速开始
28 |
29 | 请先进入你的 npm pkg 仓库,推荐:Babel、Tsc、Swc、Father 等
30 |
31 | ```sh
32 | npm i @doctors/npm-pkg
33 | doctor web-tools
34 | ```
35 |
36 | ## 配置文件 `.doctor.[ts|js]`
37 |
38 | [详细配置见](/config/npm-pkg)
39 |
40 | ## 最佳实践
41 |
42 | publish 操作之前进行 doctor 质量检测
43 |
44 | ```diff
45 | + "scripts":{
46 | - "prepublishOnly": "npm run build"
47 | + "prepublishOnly": "doctor npm-pkg && npm run build"
48 | + }
49 | ```
50 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/type.ts:
--------------------------------------------------------------------------------
1 | import type { IApi as DoctorApi, DoctorMeta } from "@doctors/core";
2 | import { DoctorLevel } from "@doctors/core";
3 | import { SourceFile } from "@doctors/utils";
4 |
5 | // 元数据
6 | export interface Meta {
7 | sourceFiles?: SourceFile[];
8 | }
9 |
10 | export type IApi = DoctorApi &
11 | Meta & {
12 | addDoctorNpmPkgCheckBefore: (
13 | fn: (meta: Meta) => DoctorMeta[] | DoctorMeta | undefined
14 | ) => void;
15 |
16 | addDoctorNpmPkgCheck: (
17 | fn: (meta: Meta) => DoctorMeta[] | DoctorMeta | undefined
18 | ) => void;
19 |
20 | addDoctorNpmPkgCheckEnd: (
21 | fn: (meta: Meta) => DoctorMeta[] | DoctorMeta | undefined
22 | ) => void;
23 | } & {
24 | userConfig: ConfigSchema;
25 | };
26 |
27 | export interface ConfigSchema {
28 | npmPkg?: {
29 | peerDepAndDepRepeat?: {
30 | level?: DoctorLevel;
31 | exclude?: string[];
32 | };
33 | checkPkgFilesExist?: {
34 | level?: DoctorLevel;
35 | };
36 | preferPackFiles?: {
37 | level?: DoctorLevel;
38 | };
39 | compileFiles?: string | string[];
40 | cjs?: {
41 | open: boolean;
42 | cjsImportEsm?: {
43 | level?: DoctorLevel;
44 | };
45 | };
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/website/docs/config/npm-pkg.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: NPM 类库研发
3 | order: 2
4 | nav:
5 | title: 文档
6 | order: 2
7 | ---
8 |
9 | # `@doctors/npm-pkg`
10 |
11 | 为了更好地满足自定义诉求,增加了一些特有字段,具体配置字段如下:
12 |
13 | ## Config Schema 💻
14 |
15 | ```ts
16 | interface ConfigSchema {
17 | npmPkg?: {
18 | peerDepAndDepRepeat?: {
19 | level?: DoctorLevel;
20 | exclude?: string[];
21 | };
22 | checkPkgFilesExist?: {
23 | level?: DoctorLevel;
24 | };
25 | preferPackFiles?: {
26 | level?: DoctorLevel;
27 | };
28 | compileFiles?: string | string[];
29 | cjs?: {
30 | open: boolean;
31 | cjsImportEsm?: {
32 | level?: DoctorLevel;
33 | };
34 | };
35 | };
36 | }
37 | ```
38 |
39 | ### DoctorLevel
40 |
41 | | Level | 类型 | 描述 |
42 | | ----- | ----------------- | --------------------------- |
43 | | off | DoctorLevel.OFF | 关闭此条 doctor rule 的检测 |
44 | | warn | DoctorLevel.WARN | WARN 等级,不会打断后续进程 |
45 | | error | DoctorLevel.ERROR | Error 等级 打断后续进程 |
46 |
47 | ## 基础配置
48 |
49 | ### nodeVersion
50 |
51 | - 类型:DoctorLevel
52 | - 默认值:DoctorLevel.WARN
53 |
54 | 检测当前 Node 版本
55 |
56 | ### gitSshKey
57 |
58 | - 类型:DoctorLevel
59 | - 默认值:DoctorLevel.WARN
60 |
61 | 检测 gitSshKey 是否正确配置
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "doctors-all",
3 | "version": "0.0.37",
4 | "private": true,
5 | "packageManager": "pnpm@8.0.0",
6 | "scripts": {
7 | "preinstall": "npx only-allow pnpm",
8 | "pnpm:devPreinstall": "node ./scripts/preinstall.js && pnpm recursive exec -- rm -rf node_modules",
9 | "dev": "father dev",
10 | "build:all": "pnpm run -r build",
11 | "test:all": "pnpm run -r test",
12 | "prepare": "husky install",
13 | "release": "bumpp -r",
14 | "typecheck": "tsc --noEmit",
15 | "test": "vitest"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20.3.1",
19 | "@types/prettier": "^2.7.3",
20 | "@doctors/core": "workspace:*",
21 | "@doctors/utils": "workspace:*",
22 | "@doctors/npm-pkg": "workspace:*",
23 | "@doctors/web-tools": "workspace:*",
24 | "bumpp": "^9.1.1",
25 | "father": "^4.1.8",
26 | "husky": "^8.0.3",
27 | "lint-staged": "^13.2.2",
28 | "prettier": "2.8.8",
29 | "typescript": "^5.1.3",
30 | "vitest": "^0.32.2"
31 | },
32 | "lint-staged": {
33 | "*.{ts,tsx,md,mdx}": [
34 | "prettier --write --ignore-unknown"
35 | ],
36 | "*.{ts,tsx}": [
37 | "tsc --noEmit --skipLibCheck --esModuleInterop --downlevelIteration"
38 | ]
39 | },
40 | "keywords": [],
41 | "author": "",
42 | "license": "MIT"
43 | }
44 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/commands/npm-pkg.ts:
--------------------------------------------------------------------------------
1 | import { generatePreset, type Nullify } from "@doctors/core";
2 | import { getFilesWithImports } from "@doctors/utils";
3 | import { type IApi, type Meta } from "../type";
4 | import { ConfigSchema } from "../type";
5 | import { PRESET_NAME } from "../constants";
6 |
7 | const schema: Nullify = {
8 | npmPkg: {
9 | peerDepAndDepRepeat: {
10 | level: null,
11 | exclude: null,
12 | },
13 | checkPkgFilesExist: {
14 | level: null,
15 | },
16 | preferPackFiles: {
17 | level: null,
18 | },
19 | compileFiles: null,
20 | // 开启该选项表示项目为cjs规范,会启动cjs相关的检查
21 | // cjs添加其他字段 TODO
22 | cjs: {
23 | open: null,
24 | cjsImportEsm: {
25 | level: null,
26 | },
27 | },
28 | },
29 | };
30 |
31 | export default async (api: IApi) => {
32 | // meta 元数据 将会作为所有 feature 插件的实参传入 供使用
33 | const meta: Meta = {};
34 |
35 | let compileFiles: string[] = [];
36 |
37 | if (api.userConfig.npmPkg?.compileFiles) {
38 | compileFiles = Array.isArray(api.userConfig?.npmPkg?.compileFiles)
39 | ? api.userConfig?.npmPkg?.compileFiles
40 | : [api.userConfig?.npmPkg?.compileFiles];
41 | }
42 |
43 | const sourceFiles = await getFilesWithImports(compileFiles, api.cwd);
44 |
45 | meta.sourceFiles = sourceFiles;
46 |
47 | generatePreset({
48 | api,
49 | command: PRESET_NAME,
50 | schema,
51 | meta,
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/features/depInPeerDependencies.ts:
--------------------------------------------------------------------------------
1 | import type { IApi } from "../type";
2 | import { DoctorLevel, DoctorMeta } from "@doctors/core";
3 |
4 | // Project dependencies and peerDependencies repeat Times warnings
5 | export default function depInPeerDependencies(api: IApi) {
6 | api.addDoctorNpmPkgCheck(() => {
7 | const userConfig = api.userConfig;
8 | const warns: DoctorMeta[] = [];
9 |
10 | if (api.pkg.peerDependencies && api.pkg.dependencies) {
11 | Object.keys(api.pkg.peerDependencies).forEach((pkg) => {
12 | if (
13 | api.pkg.dependencies![pkg] &&
14 | !userConfig.npmPkg?.peerDepAndDepRepeat?.exclude?.includes(pkg)
15 | ) {
16 | warns.push({
17 | label: "depInPeerDependencies",
18 | description: `The package ${pkg} is both a peerDependency and a dependency,Please remove one from the package.json file base on project requirements`,
19 | doctorLevel:
20 | userConfig.npmPkg?.peerDepAndDepRepeat?.level ||
21 | DoctorLevel.ERROR,
22 | });
23 | }
24 | });
25 | }
26 |
27 | if (!warns.length) {
28 | return {
29 | label: "depInPeerDependencies",
30 | description:
31 | "Package with duplicate peerDependencies and dependencies were not detected",
32 | doctorLevel: DoctorLevel.SUCCESS,
33 | };
34 | }
35 |
36 | return warns;
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/packages/utils/src/features/scanningParser.ts:
--------------------------------------------------------------------------------
1 | import { build, type OnResolveArgs } from "esbuild";
2 | import fs from "fs";
3 | import path from "path";
4 | import { getCache } from "@doctors/utils";
5 |
6 | export type IDoctorSourceParseResult = {
7 | imports: Omit[];
8 | };
9 |
10 | export default async (
11 | fileAbsPath: string
12 | ): Promise => {
13 | const cache = getCache("doctor-parser");
14 | // format: {path:mtime}
15 | const cacheKey = [fileAbsPath, fs.statSync(fileAbsPath).mtimeMs].join(":");
16 | const cacheRet = cache.getSync(cacheKey, "");
17 | const ret: IDoctorSourceParseResult = { imports: [] };
18 |
19 | if (cacheRet) return cacheRet;
20 |
21 | await build({
22 | // 不写入磁盘,扫描文件收集元信息供 features 使用
23 | write: false,
24 | // 触发 onResolve hook
25 | bundle: true,
26 | logLevel: "silent",
27 | format: "esm",
28 | target: "esnext",
29 | // esbuild need relative entry path
30 | entryPoints: [path.basename(fileAbsPath)],
31 | absWorkingDir: path.dirname(fileAbsPath),
32 | plugins: [
33 | {
34 | name: "plugin-scanning-doctor",
35 | setup: (builder) => {
36 | builder.onResolve({ filter: /.*/ }, ({ pluginData, ...args }) => {
37 | if (args.kind !== "entry-point") {
38 | ret.imports.push(args);
39 |
40 | return {
41 | path: args.path,
42 | // make all deps external
43 | external: true,
44 | };
45 | }
46 | });
47 | },
48 | },
49 | ],
50 | });
51 |
52 | cache.set(cacheKey, ret);
53 |
54 | return ret;
55 | };
56 |
--------------------------------------------------------------------------------
/packages/core/src/cli/cli.ts:
--------------------------------------------------------------------------------
1 | import { deepmerge, yParser, logger } from "@umijs/utils";
2 | import { BUILD_COMMANDS, DEV_COMMAND } from "../constants";
3 | import { Service } from "../service/service";
4 | import {
5 | checkLocal,
6 | checkVersion as checkNodeVersion,
7 | setNoDeprecation,
8 | setNodeTitle,
9 | } from "./node";
10 |
11 | interface IOpts {
12 | args?: yParser.Arguments;
13 | }
14 |
15 | export async function run(_opts?: IOpts) {
16 | checkNodeVersion();
17 | checkLocal();
18 | setNodeTitle();
19 | setNoDeprecation();
20 |
21 | const args =
22 | _opts?.args ||
23 | yParser(process.argv.slice(2), {
24 | alias: {
25 | version: ["v"],
26 | help: ["h"],
27 | },
28 | boolean: ["version"],
29 | });
30 | const command = args._[0];
31 |
32 | if (command === DEV_COMMAND) {
33 | process.env.NODE_ENV = "development";
34 | // handle ctrl+c and exit with 0, to avoid pnpm exit with error
35 | /* istanbul ignore next -- @preserve */
36 | process.on("SIGINT", () => process.exit(0));
37 | } else if (BUILD_COMMANDS.includes(command)) {
38 | process.env.NODE_ENV = "production";
39 | }
40 |
41 | // set quiet mode for logger
42 | if (args.quiet) logger.info(args.quiet);
43 |
44 | try {
45 | const service = new Service();
46 |
47 | await service.run2({
48 | name: command,
49 | args: deepmerge({}, args),
50 | });
51 |
52 | // handle restart for dev command
53 | if (command === DEV_COMMAND) {
54 | async function listener(data: any) {
55 | if (data?.type === "RESTART") {
56 | // off self
57 | process.off("message", listener);
58 |
59 | // restart
60 | run({ args });
61 | }
62 | }
63 |
64 | process.on("message", listener);
65 | }
66 | } catch (e: any) {
67 | logger.error(e);
68 | process.exit(1);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/core/src/generateDiy.ts:
--------------------------------------------------------------------------------
1 | import { IApi } from "./types";
2 | const { spawn } = require("child_process");
3 |
4 | interface GenerateDiyProps {
5 | api: IApi;
6 | presets: string[];
7 | commands: string[];
8 | }
9 | export default function generateDiy({
10 | api,
11 | presets,
12 | commands: diyCommands,
13 | ...restProps
14 | }: GenerateDiyProps) {
15 | api.registerCommand({
16 | name: "diy",
17 | async fn() {
18 | const { spinner: load } = await import("@astrojs/cli-kit");
19 | let hasError = false;
20 | let output: string[] = [];
21 | async function run() {
22 | const commands = diyCommands;
23 |
24 | // for cancel single default preset animation
25 | process.env.IS_DIY_PRESET = "true";
26 |
27 | for (const command of commands) {
28 | const ptyProcess = await spawn(
29 | "npx",
30 | ["doctor", command, "-s", "--color"],
31 | { stdio: "pipe" }
32 | );
33 | if (ptyProcess && ptyProcess.stdout) {
34 | ptyProcess.stdout.on("data", function (data) {
35 | output.push(data);
36 | });
37 | }
38 | await new Promise((resolve) => {
39 | if (ptyProcess) {
40 | ptyProcess.on("exit", (code) => {
41 | if (code === 1) {
42 | hasError = true;
43 | }
44 | resolve(void 0);
45 | });
46 | }
47 | });
48 | }
49 | }
50 | await load({
51 | start: "Doctor Rules Checking ",
52 | end: "Check end",
53 | while: () => {
54 | return run();
55 | },
56 | });
57 | function printOutput(index) {
58 | if (index < output.length) {
59 | process.stdout.write(output[index]);
60 | setTimeout(() => printOutput(index + 1), 100);
61 | } else {
62 | if (hasError) {
63 | process.exit(1);
64 | }
65 | }
66 | }
67 |
68 | printOutput(0);
69 | },
70 | });
71 |
72 | return {
73 | presets,
74 | ...restProps,
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/features/cjsImportEsm.ts:
--------------------------------------------------------------------------------
1 | import { chalk, winPath } from "@umijs/utils";
2 | import enhancedResolve from "enhanced-resolve";
3 | import vm from "vm";
4 | import { DoctorLevel, DoctorMeta } from "@doctors/core";
5 | import { getPkgNameFromPath } from "@doctors/utils";
6 | import type { ConfigSchema, IApi } from "../type";
7 |
8 | export default (api: IApi) => {
9 | api.addDoctorNpmPkgCheck(({ sourceFiles }) => {
10 | const userConfig = api.userConfig as ConfigSchema;
11 | const warns: DoctorMeta[] = [];
12 | const sandbox = vm.createContext({ require });
13 | let resolver: ReturnType<(typeof enhancedResolve)["create"]["sync"]>;
14 |
15 | if (!userConfig.npmPkg?.cjs?.open) {
16 | return warns;
17 | }
18 |
19 | sourceFiles?.forEach((file) => {
20 | file.imports?.forEach((i) => {
21 | resolver ??= enhancedResolve.create.sync({});
22 | const pkgName = getPkgNameFromPath(i.path);
23 |
24 | if (pkgName && i.kind === "import-statement") {
25 | try {
26 | const res = resolver(i.resolveDir, i.path);
27 | // 只检查npm包
28 | if (res && res.includes("node_modules")) {
29 | vm.runInContext(`require('${winPath(res)}')`, sandbox);
30 | }
31 | } catch (e: any) {
32 | if (e.code === "ERR_REQUIRE_ESM") {
33 | warns.push({
34 | label: "cjsImportEsm",
35 | description: `CommonJS file import an ES Module \`${
36 | i.path
37 | }\`, it will cause \`ERR_REQUIRE_ESM\` error in Node.js runtime
38 | ${chalk.gray(
39 | `at ${file.path}`
40 | )}, Please convert import to await import, or find a CommonJS version of the package`,
41 | doctorLevel:
42 | userConfig.npmPkg?.cjs?.cjsImportEsm?.level ||
43 | DoctorLevel.ERROR,
44 | });
45 | }
46 | }
47 | }
48 | });
49 | });
50 |
51 | if (!warns.length) {
52 | return {
53 | label: "cjsImportEsm",
54 | description: "The CommonJS file don not import ES Module",
55 | doctorLevel: DoctorLevel.SUCCESS,
56 | };
57 | }
58 |
59 | return warns;
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/website/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "dumi";
2 | import type { SiteThemeConfig } from "dumi-theme-antd-style";
3 | import path from "path";
4 |
5 | import { homepage, name } from "../package.json";
6 |
7 | import { featuresZh } from "./config/features";
8 |
9 | const isProd = process.env.NODE_ENV === "production";
10 |
11 | const themeConfig: SiteThemeConfig = {
12 | name: "Doctor",
13 | logo: "https://gw.alipayobjects.com/zos/hitu-asset/c88e3678-6900-4289-8538-31367c2d30f2/hitu-1609235995955-image.png",
14 |
15 | hero: {
16 | "zh-CN": {
17 | description: "Doctor - 你的研发质量保障",
18 | actions: [
19 | {
20 | type: "primary",
21 | text: "开始使用",
22 | link: "/guide",
23 | },
24 | {
25 | text: "Github",
26 | link: "https://github.com/FE-Struggler/doctor",
27 | openExternal: true,
28 | },
29 | ],
30 | features: featuresZh,
31 | },
32 | "en-US": {
33 | description: "dumi2 theme similar to antd v5 website",
34 | actions: [
35 | {
36 | type: "primary",
37 | text: "Start",
38 | link: "/guide-en",
39 | },
40 | {
41 | text: "Config",
42 | link: "/config-en",
43 | },
44 | ],
45 | },
46 | },
47 | socialLinks: { github: homepage },
48 | apiHeader: {
49 | pkg: name,
50 | sourceUrl: `{github}/tree/master/src/components/{atomId}/index.tsx`,
51 | docUrl: `{github}/tree/master/example/docs/components/{atomId}.{locale}.md`,
52 | },
53 | footer: "Made with ❤️ by every FE Struggler",
54 | };
55 |
56 | export default defineConfig({
57 | themeConfig,
58 | html2sketch: {},
59 | favicons: [
60 | "https://gw.alipayobjects.com/zos/hitu-asset/c88e3678-6900-4289-8538-31367c2d30f2/hitu-1609235995955-image.png",
61 | ],
62 | locales: [
63 | { id: "zh-CN", name: "中文", suffix: "" },
64 | { id: "en-US", name: "English", suffix: "-en" },
65 | ],
66 | alias: {
67 | "dumi-theme-antd-style": path.join(__dirname, "../src"),
68 | },
69 | styles: [
70 | `html, body { background: transparent; }
71 |
72 | @media (prefers-color-scheme: dark) {
73 | html, body { background: #0E1116; }
74 | }`,
75 | ],
76 | codeSplitting: {
77 | jsStrategy: "granularChunks",
78 | },
79 | // @ts-ignore
80 | ssr: isProd ? {} : false,
81 | });
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Doctors - For Web Development
4 |
5 |
6 | 研发质量保障的好帮手
7 |
8 |
9 |
10 |
11 | 📚 官方文档
12 |
13 |
14 |
15 | ## 上手尝试
16 |
17 | 在本项目的目录打开 `terminal`
18 |
19 | ```sh
20 | # 构建
21 | pnpm i # 请使用 pnpm 8
22 | npm run build:all
23 |
24 | # 使用示例
25 | cd examples/web-tools
26 | npm run test
27 | ```
28 |
29 | `examples/diy` 整合了目前已有的绝大部分 rules
30 |
31 | ```sh
32 | cd examples/diy
33 | npm run test
34 | ```
35 |
36 |
37 |
38 | ## 开发指南
39 |
40 | ### 入门案例
41 |
42 | 对 `web-tools` 进行 `feature` 开发。
43 |
44 | ```sh
45 | cd packages/web-tools
46 | npm run dev
47 | ```
48 |
49 | 开启 `dev` 之后,修改该目录下代码会自动更新。本项目是 `Monorepo` 架构,因此可以直接在 `examples/web-tools` 下直接运行 `npm run doctor:webtools` 进行调试。
50 |
51 | 如果想为其添加新 `feature`,请参照 `packages/web-tools/src/features` 中的代码格式,如下。
52 |
53 | ```ts
54 | // `packages/web-tools/src/features/checkXxx.ts`
55 | export default (api: IApi) => {
56 | api.addDoctorWebToolsCheck(async () => {
57 | // 你的判断逻辑
58 | return {
59 | label: "{{feature-name}}",
60 | description: "{{description}}",
61 | // 提示信息等级 ENUM: OFF | WARN | SUCCESS | ERROR
62 | doctorLevel: DoctorLevel.WARN,
63 | };
64 | });
65 | };
66 | ```
67 |
68 | 并将其统一受 `features/index.ts` 中暴露。
69 |
70 | ```ts
71 | export default [...+require.resolve("./checkXxx")];
72 | ```
73 |
74 | ### 开发一个新的 doctor
75 |
76 | 打开 `terminal`,按如下步骤(无需在本项目的 `packages` 下开发)
77 |
78 | ```sh
79 | mkdir doctor-xxx
80 | cd doctor-xxx
81 | npx create-doctor
82 | ```
83 |
84 | 选择 **`preset`**,并根据提示输入即可。
85 |
86 | 配置可参照 `packages/web-tools`。
87 |
88 | 开发进行时同样可以使用
89 |
90 | ```sh
91 | npm run dev
92 | ```
93 |
94 | 为了能够快速 `debug`,建议以 `Monorepo` 的形式开发,如本项目中的 `examples` 目录。
95 |
96 | ### Windows 开发注意事项
97 |
98 | windows 环境下开发可能会遇到一些问题,可以参考:
99 |
100 | 1. 确保 node 版本为 16+,pnpm 版本为 8+
101 | 2. 部分命令报错时(如`sh`, `rm -rf`等)请使用能够支持这些 shell 的终端运行命令,比如`git-bash`,`zsh`等
102 | 3. 依赖下载失败时,考虑切换下载源,开启管理员模式后重试
103 | 4. 提示`doctor`等依赖缺失时,请尝试 `pnpm i`, `doctor`命令由本地的 `@doctors/core` 提供,请检查是否正确使用 `Monorepo` 模式开发
104 |
105 |
106 |
107 | ## 文档贡献
108 |
109 | 本项目文档基于 **`dumi` + `Ant Design`主题包** 开发。
110 |
111 | 进入`website/`,可做如下工作
112 |
113 | 1. 修改 `.dumirc.ts` 配置文件。`Powered by And Design`主题包。
114 | 2. 编写 `Markdown` 文档。`Powered by dumi`。
115 |
116 | 启动
117 |
118 | ```sh
119 | npm run start
120 | ```
121 |
--------------------------------------------------------------------------------
/packages/npm-pkg/src/features/checkPkgFilesExist.ts:
--------------------------------------------------------------------------------
1 | import type { ConfigSchema, IApi } from "../type";
2 | import { DoctorLevel, DoctorMeta } from "@doctors/core";
3 | import path from "path";
4 | import fs from "fs";
5 | import { globSync } from "glob";
6 |
7 | const outputFileType = ["js", "jsx", "ts", "tsx", "html", "css"];
8 |
9 | export default (api: IApi) => {
10 | api.addDoctorNpmPkgCheck(() => {
11 | const userConfig = api.userConfig as ConfigSchema;
12 | const absolutePath: string[] = [];
13 | const warns: DoctorMeta[] = [];
14 |
15 | api.pkg.files?.forEach((file: string) => {
16 | absolutePath.push(path.join(api.cwd, file));
17 | });
18 |
19 | if (!absolutePath.length) {
20 | return;
21 | }
22 |
23 | for (let path of absolutePath) {
24 | if (fs.existsSync(path)) {
25 | const stat = fs.statSync(path);
26 | if (stat.isDirectory()) {
27 | const files = globSync(path + `/*.{${outputFileType.join(",")}}`);
28 | if (!files.length) {
29 | warns.push({
30 | label: "checkPkgFilesExist",
31 | description: `The output entity \'${path}\` is in the \`files\` field of the package.json but the directory is empty, please check whether the package is successfully packaged`,
32 | doctorLevel:
33 | userConfig.npmPkg?.checkPkgFilesExist?.level ||
34 | DoctorLevel.ERROR,
35 | });
36 | }
37 | }
38 | } else if (/^.*\*\.[a-zA-Z0-9]+$/.test(path)) {
39 | const files = globSync(path);
40 | if (!files.length) {
41 | warns.push({
42 | label: "checkPkgFilesExist",
43 | description: `The output entity \'${path}\` is in the \`files\` field of the package.json but the directory is No file name extension is specified in the folder, please check whether the package is successfully packaged`,
44 | doctorLevel:
45 | userConfig.npmPkg?.checkPkgFilesExist?.level || DoctorLevel.ERROR,
46 | });
47 | }
48 | } else {
49 | warns.push({
50 | label: "checkPkgFilesExist",
51 | description: `The output entity \'${path}\` is in the \`files\` field of the package.json file but no exist, please check whether the package is successfully packaged`,
52 | doctorLevel:
53 | userConfig.npmPkg?.checkPkgFilesExist?.level || DoctorLevel.ERROR,
54 | });
55 | }
56 | }
57 |
58 | if (!warns.length) {
59 | return {
60 | label: "checkPkgFilesExist",
61 | description:
62 | "The output entity in the files field of the packages.json file is normal",
63 | doctorLevel: DoctorLevel.SUCCESS,
64 | };
65 | }
66 |
67 | return warns;
68 | });
69 | };
70 |
--------------------------------------------------------------------------------
/packages/create-doctor/src/index.ts:
--------------------------------------------------------------------------------
1 | import { prompts, BaseGenerator, yParser, getGitInfo } from "@umijs/utils";
2 | import { join } from "path";
3 | import { rename } from "fs/promises";
4 | import { isKebabCase, kebabToCamelCase, promptsWithCancel } from "./utils";
5 |
6 | enum ETemplate {
7 | preset = "preset",
8 | feature = "feature",
9 | }
10 |
11 | interface IGeneratorOpts {
12 | cwd: string;
13 | args: yParser.Arguments;
14 | }
15 |
16 | const questions: prompts.PromptObject[] = [
17 | {
18 | name: "packageName",
19 | type: "text",
20 | message: "请输入 npm 包名(请以 doctors 或 @doctors 开头)",
21 | },
22 | {
23 | name: "description",
24 | type: "text",
25 | message: "请输入项目描述",
26 | },
27 | ];
28 |
29 | export default async ({ cwd, args }: IGeneratorOpts) => {
30 | let [name] = args._;
31 | let template = ETemplate.preset;
32 |
33 | const { username, email } = await getGitInfo();
34 | const author = email && username ? `${username} <${email}>` : "";
35 |
36 | template = (
37 | await promptsWithCancel({
38 | name: "template",
39 | type: "select",
40 | message: "你是想新开发一整套预设?还是想在已有的预设上开发新 feature",
41 | choices: [
42 | { title: "preset", value: "preset" },
43 | {
44 | title: "feature",
45 | value: "feature",
46 | },
47 | ],
48 | })
49 | ).template as ETemplate;
50 |
51 | const commandAnswer = await promptsWithCancel({
52 | name: "command",
53 | type: "text",
54 | message:
55 | template === ETemplate.preset
56 | ? "请输入 doctor 命令(例如:web-tools)"
57 | : "请输入 feature 名称(例如:web-tools)",
58 | });
59 | const command = commandAnswer.command;
60 | questions.forEach((item) => {
61 | if (item.name === "packageName") {
62 | item.initial = "doctors-" + template + "-" + command;
63 | }
64 | });
65 | const commandCamelCased = isKebabCase(command)
66 | ? kebabToCamelCase(command)
67 | : "";
68 |
69 | const data =
70 | template === ETemplate.preset
71 | ? {
72 | command,
73 | commandCamelCased,
74 | commandFile: command ? command : "command",
75 | }
76 | : { command, commandCamelCased };
77 |
78 | const target = name ? join(cwd, name) : cwd;
79 | const path =
80 | template === ETemplate.preset
81 | ? join(__dirname, "../templates/preset/")
82 | : join(__dirname, "../templates/feature/");
83 |
84 | const generator = new BaseGenerator({
85 | path,
86 | target,
87 | data: {
88 | author,
89 | ...data,
90 | },
91 | questions,
92 | });
93 |
94 | await generator.run();
95 |
96 | if (template === ETemplate.preset && command) {
97 | await rename(
98 | join(target, "./src/commands/command.ts"),
99 | join(target, `./src/commands/${command}.ts`)
100 | );
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/packages/utils/src/common.ts:
--------------------------------------------------------------------------------
1 | import { globSync } from "glob";
2 | import path from "path";
3 | import fs from "fs";
4 | import lodash from "lodash";
5 | import sourceParser from "./features/scanningParser";
6 | import { DEFAULT_SOURCE_IGNORES } from "./constants";
7 | import { SourceFile } from "./types";
8 |
9 | export function getPkgNameFromPath(p: string) {
10 | return p.match(/^(?:@[a-z\d][\w-.]*\/)?[a-z\d][\w-.]*/i)?.[0];
11 | }
12 |
13 | //----------------------------- Source ---------------------------------
14 | export function getSourceDirs(files: string[]) {
15 | const configDirs = lodash.uniq([...files.map((c) => path.dirname(c))]);
16 |
17 | return configDirs;
18 | }
19 |
20 | export function getSourceFiles(compileFiles: string[], cwd: string) {
21 | let files: string[] = [];
22 |
23 | if (compileFiles.length) {
24 | compileFiles.forEach((e) => {
25 | globSync(e, {
26 | cwd,
27 | ignore: DEFAULT_SOURCE_IGNORES,
28 | nodir: true,
29 | }).forEach((file) => {
30 | files.push(file);
31 | });
32 | });
33 | } else {
34 | files = globSync("**/*.{ts,js}", {
35 | cwd,
36 | ignore: DEFAULT_SOURCE_IGNORES,
37 | nodir: true,
38 | });
39 | }
40 |
41 | const sourceDirs = getSourceDirs(files);
42 |
43 | let allFiles: string[] = sourceDirs.reduce(
44 | (ret: string[], dir: string) =>
45 | ret.concat(
46 | globSync(`${dir}/**/*.{ts,js}`, {
47 | cwd,
48 | ignore: DEFAULT_SOURCE_IGNORES,
49 | nodir: true,
50 | })
51 | ),
52 | []
53 | );
54 |
55 | allFiles = [...new Set(allFiles)];
56 |
57 | return allFiles;
58 | }
59 |
60 | export async function getFilesWithImports(
61 | compileFiles: string[],
62 | AbsDir: string
63 | ) {
64 | const files: string[] = getSourceFiles(compileFiles, AbsDir);
65 |
66 | let sourceFiles: SourceFile[];
67 |
68 | sourceFiles = await Promise.all(
69 | files.map(async (file) => {
70 | return {
71 | path: file,
72 | imports: (await sourceParser(path.join(AbsDir, file))).imports,
73 | content: fs.readFileSync(path.join(AbsDir, file), "utf-8"),
74 | };
75 | })
76 | );
77 |
78 | return sourceFiles;
79 | }
80 |
81 | //----------------------------- MonoRepo ---------------------------------
82 | export function getMonorepoSonPackages(workSpaces: string[], cwd: string) {
83 | const packAgeJsonFiles: string[] = [];
84 | workSpaces.forEach((workSpace) => {
85 | packAgeJsonFiles.push(
86 | ...globSync(workSpace + "/**/*.json", {
87 | cwd,
88 | ignore: DEFAULT_SOURCE_IGNORES,
89 | nodir: true,
90 | }).filter((jsonFile) => {
91 | return jsonFile.endsWith("package.json");
92 | })
93 | );
94 | });
95 |
96 | const packages = getSourceDirs(packAgeJsonFiles);
97 |
98 | return packages;
99 | }
100 |
--------------------------------------------------------------------------------
/website/docs/config/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Config
3 | nav:
4 | title: Config
5 | order: 4
6 | ---
7 |
8 | `dumi-theme-antd-style` For better customizability, some special fields are added, and they are placed in the `themeConfig` field of the `dumi` theme configuration item. The specific configuration fields are as follows:
9 |
10 | ## Basic Config
11 |
12 | ### github
13 |
14 | - type: `string`
15 | - default: `null`
16 |
17 | The navigation bar Github icon link, if this field is not configured, it will not be displayed.
18 |
19 | ### description
20 |
21 | - type: `string | Record`
22 | - default: `null`
23 |
24 | 配置首页首屏区域的简介文字。
25 |
26 | ### actions
27 |
28 | - type: `IAction[] | Record`
29 | - default: `null`
30 |
31 | ```ts
32 | interface IAction {
33 | /** 按钮文字描述 */
34 | text: string;
35 | /** 按钮链接 */
36 | link: string;
37 | }
38 |
39 | export default defineConfig({
40 | themeConfig: {
41 | // actions: [{text: '开始使用', link: '/guide/introduce'}]
42 |
43 | actions: {
44 | "zh-CN": [{ text: "开始使用", link: "/guide/introduce" }],
45 | "en-US": [{ text: "Start", link: "/guide/introduce-en" }],
46 | },
47 | },
48 | });
49 | ```
50 |
51 | 配置首页首屏区域的操作按钮。
52 |
53 | ### features
54 |
55 | - type: `IFeature[] | Record`
56 | - default: `null`
57 |
58 | ```ts
59 | export default defineConfig({
60 | themeConfig: {
61 | // 单语言时配置数组即可
62 | // features: [{ title: '开箱即用'}, { description: '接入简单,安装即使用,全面融入 Ant Design 5.0 风格。'}]
63 | // 多语言时配置对象,key 为语言名
64 | features: {
65 | "zh-CN": [
66 | { title: "开箱即用" },
67 | { description: "接入简单,安装即使用,全面融入 Ant Design 5.0 风格。" },
68 | ],
69 | "en-US": [
70 | { title: "Simple Use" },
71 | {
72 | description:
73 | "Simple access, installation and use, fully integrated into Ant Design 5.0 style.",
74 | },
75 | ],
76 | },
77 | },
78 | });
79 | ```
80 |
81 | 该配置底层使用本主题包的 Feature 组件,详见 [Features](/components/features) 文档。
82 |
83 | ## 高级配置
84 |
85 | ### apiHeader
86 |
87 | 搭配组件文档中的 atomId 字段
88 |
89 | - type: `ApiHeaderConfig | false`
90 | - default: `undefined`
91 |
92 | ```ts
93 | interface ApiHeaderConfig {
94 | pkg: string;
95 | match?: string[];
96 | sourceUrl?: string | false;
97 | docUrl?: string | false;
98 | }
99 |
100 | export default defineConfig({
101 | themeConfig: {
102 | apiHeader: {
103 | // 组件库包名,可以从 package.json 中引入名称
104 | pkg: "dumi-theme-antd-style",
105 | // 匹配路由,默认为 /api 或 /components
106 | match: ["/api", "/components"],
107 | // github 会匹配 themeConfig.github 字段
108 | sourceUrl: `{github}/tree/master/src/components/{atomId}/index.tsx`,
109 | docUrl: `{github}/tree/master/example/docs/components/{atomId}.{locale}.md`,
110 | },
111 | },
112 | });
113 | ```
114 |
115 | sourceUrl 和 docUrl 可以尝试匹配的动态字段有:
116 |
117 | - `github`: 在 themeConfig 中配置的 github 字段;
118 | - `atomId`: 在 markdown 文件中标记的 atomId ;
119 | - `title`: 在 markdown 文件中标记的 title 字段 ;
120 |
--------------------------------------------------------------------------------
/packages/core/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { deepmerge, resolve } from "@umijs/utils";
3 | import { IApi } from "../types";
4 | import type { Root } from "@umijs/utils/compiled/@hapi/joi";
5 | import { Config } from "@umijs/core";
6 |
7 | interface Schema {
8 | [ket: string]: any;
9 | }
10 |
11 | function generateSchema(userSchemas: Object, root_Joi?: Root) {
12 | const schema: Schema = {};
13 | for (const [key, value] of Object.entries(userSchemas)) {
14 | if (typeof value === "object" && value !== null) {
15 | schema[key] = (Joi: Root) => {
16 | if (!root_Joi) {
17 | const root_Joi = Joi;
18 | return Joi?.object(generateSchema(value, root_Joi));
19 | } else {
20 | return root_Joi?.object(generateSchema(value, root_Joi));
21 | }
22 | // if (!root_Joi) {
23 | // const root_Joi = Joi
24 | // return Joi?.object(generateSchema(value, root_Joi));
25 | // } else {
26 | // return root_Joi?.object(generateSchema(value, root_Joi));
27 | // }
28 | };
29 | } else {
30 | schema[key] = (Joi: Root) => {
31 | if (!root_Joi) {
32 | return Joi?.any();
33 | } else {
34 | return root_Joi?.any();
35 | }
36 | };
37 | }
38 | }
39 | return schema;
40 | }
41 |
42 | function getSchemas(userSchemas: Object): Record any> {
43 | return {
44 | ...generateSchema(userSchemas),
45 | };
46 | }
47 |
48 | export function applyConfigFromSchema(api: IApi, userSchemas: Object) {
49 | const schemas = getSchemas(userSchemas);
50 | for (const key of Object.keys(schemas)) {
51 | const config: Record = {
52 | schema: schemas[key] || ((joi: any) => joi.any()),
53 | };
54 | api.registerPlugins([
55 | {
56 | id: `virtual: config-${key}`,
57 | key: key,
58 | config,
59 | },
60 | ]);
61 | }
62 |
63 | // support extends config
64 | api.modifyConfig((config) => parseExtendsConfig({ config, api }));
65 | }
66 |
67 | /**
68 | * parse extends option for config
69 | */
70 | function parseExtendsConfig(opts: {
71 | config: Record;
72 | resolvePaths?: string[];
73 | api: IApi;
74 | }) {
75 | let { config } = opts;
76 |
77 | const {
78 | api,
79 | resolvePaths = api.service.configManager!.files.map((f) => path.dirname(f)),
80 | } = opts;
81 |
82 | if (config.extends) {
83 | let absExtendsPath = "";
84 | const configManager = api.service.configManager!
85 | .constructor as typeof Config;
86 |
87 | // try to resolve extends path
88 | resolvePaths.some((dir) => {
89 | try {
90 | absExtendsPath = resolve.sync(config.extends, {
91 | basedir: dir,
92 | extensions: [".js", ".ts"],
93 | });
94 | return true;
95 | } catch {}
96 | });
97 |
98 | if (!absExtendsPath) {
99 | throw new Error(`Cannot find extends config file: ${config.extends}`);
100 | } else if (api.service.configManager!.files.includes(absExtendsPath)) {
101 | throw new Error(
102 | `Cannot extends config circularly for file: ${absExtendsPath}`
103 | );
104 | }
105 | // load extends config
106 | const { config: extendsConfig, files: extendsFiles } =
107 | configManager.getUserConfig({ configFiles: [absExtendsPath] });
108 |
109 | configManager.validateConfig({
110 | config: extendsConfig,
111 | schemas: api.service.configSchemas,
112 | });
113 |
114 | // try to parse nested extends config
115 | const nestedConfig = parseExtendsConfig({
116 | config: extendsConfig,
117 | resolvePaths: [path.dirname(absExtendsPath)],
118 | api,
119 | });
120 |
121 | // merge extends config & save related files
122 | config = deepmerge(nestedConfig, config);
123 | api.service.configManager!.files.push(...extendsFiles);
124 | }
125 |
126 | return config;
127 | }
128 |
--------------------------------------------------------------------------------
/website/config/features.ts:
--------------------------------------------------------------------------------
1 | import { IFeature } from "dumi-theme-antd-style";
2 |
3 | export const featuresZh: IFeature[] = [
4 | {
5 | title: "Web 工具质量 @doctors/web-tools",
6 | link: "/guide",
7 | image: "💠",
8 | description:
9 | "本预设主要用于检查 Web 开发环境常用环境、工具、VsCode插件等,同时提供版本分析、工具用途等多种最佳实践,助力每一个前端er!",
10 | row: 7,
11 | },
12 | {
13 | title: "Doctor 框架内核 @doctors/core",
14 | description:
15 | "基于 @Umijs/core 抽象出的一层,专门用于 doctors 的研发框架内核,其特点是可扩展性、可插拔插件化体系、预设随意组合、专注于 doctors 生态提供最抽象的底层扩展能力",
16 | link: "/guide/style",
17 | imageType: "light",
18 | image:
19 | "https://gw.alipayobjects.com/zos/hitu-asset/c88e3678-6900-4289-8538-31367c2d30f2/hitu-1609235995955-image.png",
20 | row: 8,
21 | },
22 | {
23 | title: "NPM 类库研发 @doctors/npm-pkg",
24 | link: "/guide/theme",
25 | description:
26 | "专注于 npm 包以及类库研发场景下的质量检测保障,为你的 npm 包开发、构建、发布流程保驾护航,尤其适用于使用 babel、tsc、swc、father 等类库平行编译场景",
27 | image:
28 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*8KE7T7l39J0AAAAAAAAAAAAADoN6AQ/original",
29 | imageType: "primary",
30 | },
31 | {
32 | title: "3分钟开发一个独一无二的预设 by create-doctor",
33 | description:
34 | "本脚手架专注于 DX,旨在为开发者提供只专注于 doctors features 开发的研发环境,得益于 @doctors/core 提供的无限拓展能力以及可插拔系统,请尽情发挥你的创造力!专注于优质的 Doctors 预设/拓展包 开发体验",
35 | link: "/guide/syntax-highlighter",
36 | image:
37 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*1qLNRrRGFsQAAAAAAAAAAAAADoN6AQ/original",
38 | imageType: "primary",
39 | row: 9,
40 | },
41 | {
42 | title: "所有项目都推荐添加的 @doctors/common",
43 | description:
44 | "本预设包含所有 Web 开发工程化项目都具备的研发质量保障流程,预计将融入 @doctors/eslint、@doctors/pkg、幽灵依赖检测等预设",
45 | image:
46 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*6sjjRa7lLhAAAAAAAAAAAAAADoN6AQ/original",
47 | imageType: "primary",
48 | link: "/components",
49 | row: 8,
50 | },
51 | {
52 | title: "ToDoList - Welcome to contribution 💪",
53 | description:
54 | "Task 列表位于 Github 的 第一个 issue 中To be continuing 💻",
55 | image: "💗",
56 | imageType: "light",
57 | row: 6,
58 | hero: true,
59 | },
60 | ];
61 |
62 | export const featuresZhForGuide: IFeature[] = [
63 | {
64 | title: "Web 工具质量 @doctors/web-tools",
65 | link: "/guide",
66 | image: "💠",
67 | description:
68 | "本预设主要用于检查 Web 开发环境常用环境、工具、VsCode插件等,同时提供版本分析、工具用途等多种最佳实践,助力每一个前端er!",
69 | row: 7,
70 | },
71 | {
72 | title: "Doctor 框架内核 @doctors/core",
73 | description:
74 | "基于 @Umijs/core 抽象出的一层,专门用于 doctors 的研发框架内核,其特点是可扩展性、可插拔插件化体系、预设随意组合、专注于 doctors 生态提供最抽象的底层扩展能力",
75 | link: "/guide/style",
76 | imageType: "light",
77 | image:
78 | "https://gw.alipayobjects.com/zos/hitu-asset/c88e3678-6900-4289-8538-31367c2d30f2/hitu-1609235995955-image.png",
79 | row: 8,
80 | },
81 | {
82 | title: "NPM 类库研发 @doctors/npm-pkg",
83 | link: "/guide/theme",
84 | description:
85 | "专注于 npm 包以及类库研发场景下的质量检测保障,为你的 npm 包开发、构建、发布流程保驾护航,尤其适用于使用 babel、tsc、swc、father 等类库平行编译场景",
86 | image:
87 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*8KE7T7l39J0AAAAAAAAAAAAADoN6AQ/original",
88 | imageType: "primary",
89 | row: 8,
90 | },
91 | {
92 | title: "3分钟开发一个独一无二的预设 by create-doctor",
93 | description:
94 | "本脚手架专注于 DX,旨在为开发者提供只专注于 doctors features 开发的研发环境,得益于 @doctors/core 提供的无限拓展能力以及可插拔系统,请尽情发挥你的创造力!专注于优质的 Doctors 预设/拓展包 开发体验",
95 | link: "/guide/syntax-highlighter",
96 | image:
97 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*1qLNRrRGFsQAAAAAAAAAAAAADoN6AQ/original",
98 | imageType: "primary",
99 | row: 9,
100 | },
101 | {
102 | title: "ToDoList - Welcome to contribution 💪",
103 | description:
104 | "Task 列表位于 Github 的 第一个 issue 中To be continuing 💻",
105 | image: "💗",
106 | imageType: "light",
107 | row: 6,
108 | hero: true,
109 | },
110 | {
111 | title: "所有项目都推荐添加的 @doctors/common",
112 | description:
113 | "本预设包含所有 Web 开发工程化项目都具备的研发质量保障流程,预计将融入 @doctors/eslint、@doctors/pkg、幽灵依赖检测等预设",
114 | image:
115 | "https://mdn.alipayobjects.com/huamei_rqvucu/afts/img/A*6sjjRa7lLhAAAAAAAAAAAAAADoN6AQ/original",
116 | imageType: "primary",
117 | link: "/components",
118 | row: 8,
119 | },
120 | ];
121 |
--------------------------------------------------------------------------------
/packages/core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { join } from "path";
2 | import { IApi, PluginMeta, RuleResItem } from "./types";
3 | import { logger } from "@umijs/utils";
4 |
5 | const fs = require("fs");
6 | const path = require("path");
7 | const childProcess = require("child_process");
8 |
9 | const notValidPackages = ["@doctors/core", "@doctors/utils"];
10 | const validPresetNamePrefix = ["@doctors", "doctors"];
11 |
12 | // 通过执行 `npm root -g` 命令获取全局 npm 目录
13 | function getGlobalNodeModulesPath() {
14 | const command = /^win/.test(process.platform) ? "npm.cmd" : "npm";
15 | const output = childProcess.execSync(`${command} root -g`).toString().trim();
16 | const globalNodeModulesPath = output.split("\n")[0];
17 | return globalNodeModulesPath;
18 | }
19 |
20 | const globalNodeModulesPath = path.join(getGlobalNodeModulesPath());
21 |
22 | export function getDoctorDependencies() {
23 | // ------------------- localDep -------------------
24 | const localPath = process.cwd();
25 | const localPresets = getDepPathsWithPkg(localPath);
26 |
27 | const npxCacheModule = join(__dirname, "../../../../");
28 | const npxCachePresets = getDepPathsWithPkg(npxCacheModule);
29 |
30 | // ------------------- globalDep -------------------
31 | const doctorsList = childProcess
32 | .execSync("npm ls -g --depth=0 --json")
33 | .toString("utf-8");
34 |
35 | const globalDeps = JSON.parse(doctorsList).dependencies;
36 | let globalPresets: PluginMeta[] = [];
37 |
38 | for (const key of Object.keys(globalDeps)) {
39 | if (
40 | validPresetNamePrefix.some(
41 | (i) => key.startsWith(i) && !notValidPackages.includes(key)
42 | )
43 | ) {
44 | const presetPath = join(globalNodeModulesPath, key);
45 | const hasCommand = fs.existsSync(
46 | path.join(presetPath, "./dist/commands")
47 | );
48 | globalPresets.push({
49 | name: key,
50 | path: presetPath,
51 | hasCommand,
52 | });
53 | }
54 | }
55 |
56 | // ------------------- outputRepeatInfo -------------------
57 | const allDeps = localPresets.concat(globalPresets, npxCachePresets);
58 | const allPresetsWithoutRepeat = allDeps
59 | .filter((obj, index, arr) => {
60 | // 使用 findIndex() 方法查找当前对象在数组中的第一个索引位置
61 | const firstIndex = arr.findIndex((o) => o.name === obj.name);
62 | if (index !== firstIndex) {
63 | logger.warn(
64 | `there are repeated Plugins named ${obj.name} with ${arr[firstIndex].path} and ${arr[index].path}\n`
65 | );
66 | }
67 | return index === firstIndex; // 只保留第一个出现的对象
68 | })
69 | .map((i) => {
70 | return {
71 | path: i.path,
72 | hasCommand: i.hasCommand,
73 | };
74 | });
75 |
76 | const sortedByHasCommand = allPresetsWithoutRepeat.sort((a, b) => {
77 | if (a.hasCommand && !b.hasCommand) {
78 | return -1; // a 排在前面
79 | } else if (!a.hasCommand && b.hasCommand) {
80 | return 1; // b 排在前面
81 | } else {
82 | return 0; // 保持原有顺序
83 | }
84 | });
85 |
86 | return sortedByHasCommand.map((i) => i.path);
87 | }
88 |
89 | function getDepPathsWithPkg(rootPath: string) {
90 | const pkgPath = path.join(rootPath, "package.json");
91 | let packageJson;
92 | if (fs.existsSync(pkgPath)) {
93 | packageJson = require(pkgPath);
94 | } else {
95 | return [];
96 | }
97 | const dependencyNames = Object.keys(packageJson?.dependencies || []);
98 | const doctorDependencyNames = dependencyNames.filter(
99 | (name) =>
100 | validPresetNamePrefix.some((i) => name.startsWith(i)) &&
101 | !notValidPackages.includes(name)
102 | );
103 | return doctorDependencyNames.map((name) => {
104 | const modulePath = path.join(rootPath, "node_modules", name);
105 | const modulePackageJson = require(path.join(modulePath, "package.json"));
106 | const hasCommand = fs.existsSync(path.join(modulePath, "./dist/commands"));
107 | return {
108 | name,
109 | version: modulePackageJson.version,
110 | path: modulePath,
111 | hasCommand,
112 | };
113 | }) as PluginMeta[];
114 | }
115 |
116 | export function applyTypeEffect(api: IApi, name: string) {
117 | [
118 | `addDoctor${name}CheckBefore`,
119 | `addDoctor${name}Check`,
120 | `addDoctor${name}CheckEnd`,
121 | ].forEach((name) => {
122 | api.registerMethod({ name });
123 | });
124 | }
125 |
126 | export function toUpperUnderscore(str) {
127 | return str
128 | .replace(/([A-Z])/g, "_$1") // 在大写字母前添加下划线
129 | .toUpperCase(); // 转换为大写字母
130 | }
131 |
132 | export function mergeObjectsByProp(arr) {
133 | const result: RuleResItem[] = [];
134 | const map = new Map();
135 | for (const obj of arr) {
136 | const key = obj["label"];
137 | if (map.has(key)) {
138 | map.get(key).descriptions.push({
139 | suggestion: obj.description,
140 | level: obj.doctorLevel,
141 | });
142 | } else {
143 | const realItem = Object.assign({}, obj);
144 | realItem.descriptions = [
145 | { suggestion: obj.description, level: obj.doctorLevel },
146 | ];
147 | map.set(key, realItem);
148 | }
149 | }
150 | map.forEach((value) => result.push(value));
151 | return result;
152 | }
153 |
--------------------------------------------------------------------------------
/website/config/footer.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | GithubOutlined,
3 | HistoryOutlined,
4 | IssuesCloseOutlined,
5 | MediumOutlined,
6 | TwitterOutlined,
7 | ZhihuOutlined,
8 | } from "@ant-design/icons";
9 |
10 | export const footer = [
11 | {
12 | title: "相关资源",
13 | items: [
14 | {
15 | title: "Ant Design Pro",
16 | url: "https://pro.ant.design",
17 | openExternal: true,
18 | },
19 | {
20 | title: "Ant Design Pro Components",
21 | url: "https://procomponents.ant.design",
22 | openExternal: true,
23 | },
24 | {
25 | title: "Umi",
26 | description: "React 应用开发框架",
27 | url: "https://umijs.org",
28 | openExternal: true,
29 | },
30 | {
31 | title: "Dumi",
32 | description: "组件/文档研发工具",
33 | url: "https://d.umijs.org",
34 | openExternal: true,
35 | },
36 | {
37 | title: "qiankun",
38 | description: "微前端框架",
39 | url: "https://qiankun.umijs.org",
40 | openExternal: true,
41 | },
42 | ],
43 | },
44 | {
45 | title: "社区",
46 | items: [
47 | {
48 | icon: ,
49 | title: "Medium",
50 | url: "http://medium.com/ant-design/",
51 | openExternal: true,
52 | },
53 | {
54 | icon: ,
55 | title: "Twitter",
56 | url: "http://twitter.com/antdesignui",
57 | openExternal: true,
58 | },
59 | {
60 | icon: (
61 |
65 | ),
66 | title: "Ant Design 语雀专栏",
67 | url: "https://yuque.com/ant-design/ant-design",
68 | openExternal: true,
69 | },
70 | {
71 | icon: ,
72 | title: "Ant Design 知乎专栏",
73 | url: "https://www.zhihu.com/column/c_1564262000561106944",
74 | openExternal: true,
75 | },
76 | {
77 | icon: ,
78 | title: "体验科技专栏",
79 | url: "http://zhuanlan.zhihu.com/xtech",
80 | openExternal: true,
81 | },
82 | {
83 | icon: (
84 |
88 | ),
89 | title: "SEE Conf",
90 | description: "SEE Conf-蚂蚁体验科技大会",
91 | url: "https://seeconf.antfin.com/",
92 | openExternal: true,
93 | },
94 | ],
95 | },
96 | {
97 | title: "帮助",
98 | items: [
99 | {
100 | icon: ,
101 | title: "GitHub",
102 | url: "https://github.com/ant-design/antd-style",
103 | openExternal: true,
104 | },
105 | {
106 | icon: ,
107 | title: "更新日志",
108 | url: "/changelog",
109 | // LinkComponent: Link,
110 | },
111 |
112 | {
113 | icon: ,
114 | title: "讨论",
115 | url: "https://github.com/ant-design/antd-style/issues",
116 | openExternal: true,
117 | },
118 | ],
119 | },
120 | {
121 | icon: (
122 |
126 | ),
127 | title: "更多产品",
128 | items: [
129 | {
130 | icon: (
131 |
135 | ),
136 | title: "语雀",
137 | url: "https://yuque.com",
138 | description: "知识创作与分享工具",
139 | openExternal: true,
140 | },
141 | {
142 | icon: (
143 |
147 | ),
148 | title: "AntV",
149 | url: "https://antv.vision",
150 | description: "数据可视化解决方案",
151 | openExternal: true,
152 | },
153 | {
154 | icon:
,
155 | title: "Egg",
156 | url: "https://eggjs.org",
157 | description: "企业级 Node.js 框架",
158 | openExternal: true,
159 | },
160 | {
161 | icon: (
162 |
166 | ),
167 | title: "Kitchen",
168 | description: "Sketch 工具集",
169 | url: "https://kitchen.alipay.com",
170 | openExternal: true,
171 | },
172 | {
173 | icon: (
174 |
178 | ),
179 | title: "蚂蚁体验科技",
180 | url: "https://xtech.antfin.com/",
181 | openExternal: true,
182 | },
183 | ],
184 | },
185 | ];
186 |
--------------------------------------------------------------------------------
/packages/core/src/generatePreset.ts:
--------------------------------------------------------------------------------
1 | import { ApplyPluginsType } from "@umijs/core/dist/types";
2 | import { applyConfigFromSchema } from "./config";
3 | import { DoctorLevel, IApi, RuleResItem } from "./types";
4 | import {
5 | applyTypeEffect,
6 | toUpperUnderscore,
7 | mergeObjectsByProp,
8 | } from "./utils";
9 | import { chalk } from "@umijs/utils";
10 |
11 | function transformString(str: string) {
12 | const parts = str.split("-");
13 | const capitalizedParts = parts.map((part) => {
14 | return part.charAt(0).toUpperCase() + part.slice(1);
15 | });
16 | return capitalizedParts.join("");
17 | }
18 |
19 | function sort(webToolsRes: RuleResItem[]) {
20 | return webToolsRes.sort((a, b) => {
21 | if (a.doctorLevel === b.doctorLevel) {
22 | return 0;
23 | } else if (a.doctorLevel === DoctorLevel.SUCCESS) {
24 | return -1;
25 | } else if (
26 | a.doctorLevel === DoctorLevel.WARN &&
27 | b.doctorLevel !== DoctorLevel.SUCCESS
28 | ) {
29 | return -1;
30 | } else if (
31 | a.doctorLevel === DoctorLevel.ERROR &&
32 | b.doctorLevel !== DoctorLevel.WARN &&
33 | b.doctorLevel !== DoctorLevel.SUCCESS
34 | ) {
35 | return -1;
36 | } else {
37 | return 1;
38 | }
39 | });
40 | }
41 |
42 | interface GeneratePresetProps {
43 | api: IApi;
44 | command: string;
45 | schema?: Object;
46 | meta?: Object;
47 | }
48 |
49 | export default function generatePreset({
50 | api,
51 | schema,
52 | command,
53 | meta,
54 | }: GeneratePresetProps) {
55 | api.describe({
56 | key: `doctor-generate-preset-fn-${command}`,
57 | });
58 | applyTypeEffect(api, transformString(command));
59 |
60 | if (schema) {
61 | applyConfigFromSchema(api, schema);
62 | }
63 |
64 | api.registerCommand({
65 | name: command,
66 | description: "start incremental build in watch mode",
67 | async fn() {
68 | //----------------- check before ------------------
69 | await api.applyPlugins({
70 | key: `addDoctor${transformString(command)}CheckBefore`,
71 | type: ApplyPluginsType.add,
72 | });
73 |
74 | if (process.env.IS_DIY_PRESET !== "true") {
75 | const { spinner: load } = await import("@astrojs/cli-kit");
76 | //----------------- checking ------------------
77 | await load({
78 | start: "Doctor Rule Checking",
79 | end: "Check end",
80 | while: () => {
81 | return new Promise(async (resolve) => {
82 | CheckingAndEnd({
83 | api,
84 | command,
85 | meta,
86 | resolve,
87 | animationFn: () =>
88 | new Promise((res) => {
89 | setTimeout(() => {
90 | res(void 0);
91 | }, 1000);
92 | }),
93 | delayFn: () =>
94 | new Promise((res) => {
95 | setTimeout(() => {
96 | res(void 0);
97 | }, 100);
98 | }),
99 | });
100 | });
101 | },
102 | });
103 | } else {
104 | CheckingAndEnd({ api, command, meta });
105 | }
106 | },
107 | });
108 | }
109 |
110 | async function CheckingAndEnd({
111 | api,
112 | command,
113 | meta,
114 | resolve = (res: undefined) => {},
115 | animationFn = async () => {},
116 | delayFn = async () => {},
117 | }) {
118 | //----------------- checking ------------------
119 | const checkedResult = (
120 | await api.applyPlugins({
121 | key: `addDoctor${transformString(command)}Check`,
122 | type: ApplyPluginsType.add,
123 | args: meta,
124 | })
125 | ).filter(Boolean);
126 |
127 | // for cli animation
128 | await animationFn();
129 | resolve(void 0);
130 |
131 | // for delay output end 100 ms
132 | await delayFn();
133 |
134 | const mergedRes = sort(mergeObjectsByProp(checkedResult.filter(Boolean)));
135 | mergedRes.forEach((rule, index) => {
136 | console.log(
137 | chalk.greenBright(
138 | `${chalk.greenBright(
139 | `${index++ < 10 ? "0" + index++ : index++}. ${toUpperUnderscore(
140 | rule.label
141 | )}`
142 | )}\n`
143 | )
144 | );
145 | rule.descriptions.forEach((i) => {
146 | switch (i?.level) {
147 | case DoctorLevel.SUCCESS:
148 | console.log(`${chalk.bgGreenBright(" DoctorLevel SUCCESS 🎉🎉 ")}`);
149 | console.log(`${chalk.greenBright(" WHY ")}${i.suggestion} \n`);
150 | break;
151 | case DoctorLevel.WARN:
152 | console.log(`${chalk.bgYellowBright("DoctorLevel WARN ")}`);
153 | console.log(`${chalk.greenBright(" SUGGESTION ")}${i.suggestion} \n`);
154 | break;
155 | case DoctorLevel.ERROR:
156 | console.log(`${chalk.bgRedBright(" DoctorLevel Error ")}`);
157 | console.log(`${chalk.greenBright(" SUGGESTION ")}${i.suggestion} \n`);
158 | break;
159 | default:
160 | break;
161 | }
162 | });
163 | });
164 |
165 | //----------------- check end ------------------
166 | if (mergedRes.some((i) => i.doctorLevel === DoctorLevel.ERROR)) {
167 | process.exit(1);
168 | }
169 |
170 | await api.applyPlugins({
171 | key: `addDoctor${transformString(command)}CheckEnd`,
172 | type: ApplyPluginsType.add,
173 | });
174 | }
175 |
--------------------------------------------------------------------------------