Skip to content

Commit

Permalink
feat: doctor add individual components required check
Browse files Browse the repository at this point in the history
  • Loading branch information
winchesHe committed Mar 31, 2024
1 parent cd87554 commit 2aa7ff9
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 82 deletions.
136 changes: 63 additions & 73 deletions src/actions/doctor-action.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import chalk from 'chalk';

import {checkApp, checkRequiredContentInstalled, checkTailwind} from '@helpers/check';
import {
checkApp,
checkRequiredContentInstalled,
checkTailwind,
combineProblemRecord
} from '@helpers/check';
import {Logger, type PrefixLogType} from '@helpers/logger';
import {getPackageInfo} from '@helpers/package';
import {findFiles} from '@helpers/utils';
import {resolver} from 'src/constants/path';
import {DOCS_APP_SETUP, DOCS_INSTALLED, DOCS_TAILWINDCSS_SETUP} from 'src/constants/required';
import {DOCS_TAILWINDCSS_SETUP} from 'src/constants/required';

interface DoctorActionOptions {
packagePath?: string;
tailwindPath?: string;
appPath?: string;
checkApp?: boolean;
checkTailwind?: boolean;
}

interface ProblemRecord {
export interface ProblemRecord {
name: string;
level: Extract<PrefixLogType, 'error' | 'warn'>;
outputFn: () => void;
Expand All @@ -22,6 +29,8 @@ interface ProblemRecord {
export async function doctorAction(options: DoctorActionOptions) {
const {
appPath = findFiles('**/app.tsx')[0],
checkApp: enableCheckApp = true,
checkTailwind: enableCheckTailwind = true,
packagePath = resolver('package.json'),
tailwindPath = findFiles('**/tailwind.config.js')
} = options;
Expand Down Expand Up @@ -60,6 +69,28 @@ export async function doctorAction(options: DoctorActionOptions) {
}
});
}
// If there is no tailwind.config.js
if (enableCheckTailwind && !tailwindPaths.length) {
problemRecord.push({
level: 'error',
name: 'missingTailwind',
outputFn: () => {
Logger.error('you have not created the tailwind.config.js');
Logger.error(`Please check the detail in the NextUI document: ${DOCS_TAILWINDCSS_SETUP}`);
}
});
}
// If there is no app.tsx
if (enableCheckApp && !appPath) {
problemRecord.push({
level: 'error',
name: 'missingApp',
outputFn: () => {
Logger.error('Cannot find the app.tsx file');
Logger.error("You should specify appPath through 'doctor --appPath=yourAppPath'");
}
});
}

/** ======================== Check if the allComponents required dependencies installed ======================== */
if (isAllComponents) {
Expand All @@ -70,79 +101,26 @@ export async function doctorAction(options: DoctorActionOptions) {
);

if (!isCorrectInstalled) {
problemRecord.push({
level: 'error',
name: 'missingDependencies',
outputFn: () => {
Logger.error('you have not installed the required dependencies');
Logger.newLine();
Logger.info('The required dependencies are:');
missingDependencies.forEach((dependency) => {
Logger.info(`- ${dependency}`);
});
Logger.newLine();
Logger.info(`Please check the detail in the NextUI document: ${DOCS_INSTALLED}`);
}
});
problemRecord.push(combineProblemRecord('missingDependencies', {missingDependencies}));
}

// Check whether tailwind.config.js is correct
if (!tailwindPaths.length) {
problemRecord.push({
level: 'error',
name: 'missingTailwind',
outputFn: () => {
Logger.error('you have not created the tailwind.config.js');
Logger.error(`Please check the detail in the NextUI document: ${DOCS_TAILWINDCSS_SETUP}`);
}
});
} else {
if (enableCheckTailwind) {
for (const tailwindPath of tailwindPaths) {
const [isCorrectTailwind, ...errorInfo] = checkTailwind('all', tailwindPath);

if (!isCorrectTailwind) {
problemRecord.push({
level: 'error',
name: 'incorrectTailwind',
outputFn: () => {
Logger.error('your tailwind.config.js is incorrect');
Logger.info('The missing part is:');
errorInfo.forEach((info) => {
Logger.info(`- need added ${info}`);
});
Logger.error(`Please check the detail in the NextUI document: ${DOCS_APP_SETUP}`);
}
});
problemRecord.push(combineProblemRecord('incorrectTailwind', {errorInfo}));
}
}
}

// Check whether the app.tsx is correct
if (!appPath) {
problemRecord.push({
level: 'error',
name: 'missingApp',
outputFn: () => {
Logger.error('Cannot find the app.tsx file');
Logger.error("You should specify appPath through 'doctor --appPath=yourAppPath'");
}
});
} else {
if (enableCheckApp && appPath) {
const [isAppCorrect, ...errorInfo] = checkApp('all', appPath);

if (!isAppCorrect) {
problemRecord.push({
level: 'error',
name: 'incorrectApp',
outputFn: () => {
Logger.error('your app.tsx is incorrect');
Logger.info('The missing part is:');
errorInfo.forEach((info) => {
Logger.info(`- need added ${info}`);
});
Logger.error(`Please check the detail in the NextUI document: ${DOCS_INSTALLED}`);
}
});
problemRecord.push(combineProblemRecord('incorrectApp', {errorInfo}));
}
}
} else if (currentComponents.length) {
Expand All @@ -153,19 +131,31 @@ export async function doctorAction(options: DoctorActionOptions) {
);

if (!isCorrectInstalled) {
problemRecord.push({
level: 'error',
name: 'missingDependencies',
outputFn: () => {
Logger.error('you have not installed the required dependencies');
Logger.newLine();
Logger.info('The required dependencies are:');
Logger.newLine();
missingDependencies.forEach((dependency) => {
Logger.info(`- ${dependency}`);
});
problemRecord.push(combineProblemRecord('missingDependencies', {missingDependencies}));
}

// Check whether tailwind.config.js is correct
if (enableCheckTailwind) {
for (const tailwindPath of tailwindPaths) {
const [isCorrectTailwind, ...errorInfo] = checkTailwind(
'partial',
tailwindPath,
currentComponents
);

if (!isCorrectTailwind) {
problemRecord.push(combineProblemRecord('incorrectTailwind', {errorInfo}));
}
});
}
}

// Check whether the app.tsx is correct
if (enableCheckApp && appPath) {
const [isAppCorrect, ...errorInfo] = checkApp('partial', appPath);

if (!isAppCorrect) {
problemRecord.push(combineProblemRecord('incorrectApp', {errorInfo}));
}
}
}

Expand Down
20 changes: 19 additions & 1 deletion src/constants/required.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {NextUIComponents} from './component';

export const FRAMER_MOTION = 'framer-motion';
export const TAILWINDCSS = 'tailwindcss';
export const NEXT_UI = '@nextui-org/react';
Expand All @@ -17,6 +19,22 @@ export const tailwindRequired = {
plugins: 'nextui()'
} as const;

export const individualTailwindRequired = {
content: (currentComponents: NextUIComponents) => {
if (currentComponents.length === 1) {
return `@nextui-org/theme/dist/components/${currentComponents[0]!.name}.js`;
}
const requiredContent = currentComponents
.reduce((acc, component) => {
return (acc += `${component.name}|`);
}, '')
.replace(/\|$/, '');

return `@nextui-org/theme/dist/components/(${requiredContent}).js`;
},
plugins: 'nextui()'
} as const;

export const appRequired = {
import: 'NextUIProvider'
};
} as const;
119 changes: 111 additions & 8 deletions src/helpers/check.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,93 @@
import type {SAFE_ANY} from './type';
import type {RequiredKey, SAFE_ANY} from './type';
import type {ProblemRecord} from 'src/actions/doctor-action';
import type {NextUIComponents} from 'src/constants/component';

import {readFileSync} from 'fs';

import {
DOCS_APP_SETUP,
DOCS_INSTALLED,
FRAMER_MOTION,
NEXT_UI,
SYSTEM_UI,
TAILWINDCSS,
THEME_UI,
appRequired,
individualTailwindRequired,
tailwindRequired
} from 'src/constants/required';

import {Logger} from './logger';
import {getMatchArray, getMatchImport} from './match';

export type CheckType = 'all' | 'partial';
export type CombineType = 'missingDependencies' | 'incorrectTailwind' | 'incorrectApp';

type DefaultCombineOptions = {
errorInfo: string[];
missingDependencies: string[];
};

type CombineOptions<T extends CombineType> = T extends 'missingDependencies'
? RequiredKey<Partial<DefaultCombineOptions>, 'missingDependencies'>
: T extends 'incorrectTailwind' | 'incorrectApp'
? RequiredKey<Partial<DefaultCombineOptions>, 'errorInfo'>
: DefaultCombineOptions;

type CheckResult<T extends SAFE_ANY[] = SAFE_ANY[]> = [boolean, ...T];

export function combineProblemRecord<T extends CombineType = CombineType>(
type: T,
options: CombineOptions<T>
): ProblemRecord {
const {errorInfo, missingDependencies} = options as DefaultCombineOptions;

if (type === 'missingDependencies') {
return {
level: 'error',
name: 'missingDependencies',
outputFn: () => {
Logger.error('You have not installed the required dependencies');
Logger.newLine();
Logger.info('The required dependencies are:');
missingDependencies.forEach((dependency) => {
Logger.info(`- ${dependency}`);
});
Logger.newLine();
Logger.info(`Please check the detail in the NextUI document: ${DOCS_INSTALLED}`);
}
};
} else if (type === 'incorrectTailwind') {
return {
level: 'error',
name: 'incorrectTailwind',
outputFn: () => {
Logger.error('Your tailwind.config.js is incorrect');
Logger.newLine();
Logger.info('The missing part is:');
errorInfo.forEach((info) => {
Logger.info(`- need added ${info}`);
});
Logger.error(`Please check the detail in the NextUI document: ${DOCS_APP_SETUP}`);
}
};
} else {
return {
level: 'error',
name: 'incorrectApp',
outputFn: () => {
Logger.error('Your app.tsx is incorrect');
Logger.newLine();
Logger.info('The missing part is:');
errorInfo.forEach((info) => {
Logger.info(`- need added ${info}`);
});
Logger.error(`Please check the detail in the NextUI document: ${DOCS_INSTALLED}`);
}
};
}
}

/**
* Check if the required content is installed
* @example return result and missing required [false, '@nextui-org/react', 'framer-motion']
Expand Down Expand Up @@ -59,15 +130,36 @@ export function checkRequiredContentInstalled(
return [false, ...result];
}

export function checkTailwind(type: CheckType, tailwindPath: string): CheckResult {
/**
* Check if the tailwind.config.js is correct
* @param type
* @param tailwindPath
* @param currentComponents
* @returns
*/
export function checkTailwind(
type: 'all',
tailwindPath: string,
currentComponents?: NextUIComponents
): CheckResult;
export function checkTailwind(
type: 'partial',
tailwindPath: string,
currentComponents: NextUIComponents
): CheckResult;
export function checkTailwind(
type: CheckType,
tailwindPath: string,
currentComponents?: NextUIComponents
): CheckResult {
const result = [] as unknown as CheckResult;

if (type === 'all') {
const tailwindContent = readFileSync(tailwindPath, 'utf-8');
const tailwindContent = readFileSync(tailwindPath, 'utf-8');

const contentMatch = getMatchArray('content', tailwindContent);
const pluginsMatch = getMatchArray('plugins', tailwindContent);
const contentMatch = getMatchArray('content', tailwindContent);
const pluginsMatch = getMatchArray('plugins', tailwindContent);

if (type === 'all') {
// Check if the required content is added Detail: https://nextui.org/docs/guide/installation#global-installation
const isDarkModeCorrect = new RegExp(tailwindRequired.darkMode).test(tailwindContent);
const isContentCorrect = contentMatch.some((content) =>
Expand All @@ -80,10 +172,21 @@ export function checkTailwind(type: CheckType, tailwindPath: string): CheckResul
if (isDarkModeCorrect && isContentCorrect && isPluginsCorrect) {
return [true];
}

!isDarkModeCorrect && result.push(tailwindRequired.darkMode);
!isContentCorrect && result.push(tailwindRequired.content);
!isPluginsCorrect && result.push(tailwindRequired.plugins);
} else if (type === 'partial') {
const individualContent = individualTailwindRequired.content(currentComponents!);
const isContentCorrect = contentMatch.some((content) => individualContent.includes(content));
const isPluginsCorrect = pluginsMatch.some((plugins) =>
plugins.includes(tailwindRequired.plugins)
);

if (isContentCorrect && isPluginsCorrect) {
return [true];
}
!isContentCorrect && result.push(individualContent);
!isPluginsCorrect && result.push(tailwindRequired.plugins);
}

return [false, ...result];
Expand All @@ -92,7 +195,7 @@ export function checkTailwind(type: CheckType, tailwindPath: string): CheckResul
export function checkApp(type: CheckType, appPath: string): CheckResult {
const result = [] as unknown as CheckResult;

if (type === 'all') {
if (type === 'all' || type === 'partial') {
const appContent = readFileSync(appPath, 'utf-8');

const importArray = getMatchImport(appContent);
Expand Down
Loading

0 comments on commit 2aa7ff9

Please sign in to comment.