├── .npmignore ├── .husky ├── pre-commit └── commit-msg ├── .eslintignore ├── .npmrc ├── resources ├── text │ ├── aim.js │ ├── love.js │ ├── description.js │ ├── art-font-logo.js │ └── examples.js ├── images │ ├── lc-cli-h.png │ ├── create-finish.png │ ├── template-page.png │ ├── service-qrcode.jpg │ ├── fulfill-template-new.png │ ├── create-new-by-template.png │ └── generating-your-template.png ├── template │ └── template.js └── headers │ ├── allQuestionRequestUrlJson.js │ ├── questionDetailJson.js │ ├── questionTagTypeJson.js │ ├── questionLanguageListJson.js │ ├── questionCodeListJson.js │ ├── questionTypeJson.js │ ├── codeSubmitJson.js │ ├── planQuestionListJson.js │ ├── questionListJson.js │ ├── studyPlanListJson.js │ ├── questionTodayJson.js │ └── questionSearchJson.js ├── common ├── origin │ ├── readme.md │ └── checkUpdate.js ├── utils │ ├── question-handler │ │ ├── getQuestionUrl.js │ │ ├── getQuestionChineseName.js │ │ ├── getQuestionFileName.js │ │ ├── createMarkdown.js │ │ ├── getConsoleText.js │ │ ├── checkQuestionByPath.js │ │ ├── getQuestionIdBySlug.js │ │ ├── createQuestionCopy.js │ │ ├── getQuestionDetail.js │ │ ├── getQuestionListCodeBy.js │ │ ├── createQuestion.js │ │ ├── parseStructure.js │ │ ├── code.js │ │ ├── fulfillQuestion.js │ │ ├── getRandomId.js │ │ ├── showLogs.js │ │ ├── getTestCase.js │ │ └── questionLanguage.js │ ├── etc │ │ ├── willUse.js │ │ ├── typeof_.js │ │ ├── checkEnv.js │ │ ├── createColorFont.js │ │ ├── open.js │ │ └── checkVisionInfo.js │ ├── file │ │ ├── parseFilePath.js │ │ ├── getDirname.js │ │ ├── getFileListBySameName.js │ │ ├── getCountBySameName.js │ │ ├── getRootPath.js │ │ ├── getFilePathById.js │ │ ├── getQuestionInDir.js │ │ └── getLineNumberByContent.js │ ├── cli-utils │ │ ├── referMode.js │ │ ├── getArgs.js │ │ ├── createQuestion.js │ │ ├── create.js │ │ └── commonMode.js │ ├── store │ │ ├── schemas │ │ │ ├── store.js │ │ │ ├── question.js │ │ │ └── allQuestion.js │ │ ├── controller │ │ │ ├── question.js │ │ │ ├── allQuestion.js │ │ │ └── store.js │ │ └── store-realm.js │ ├── question-getter │ │ ├── getQuestionLanguageList.js │ │ ├── getQuestionList.js │ │ ├── getQuestionTagType.js │ │ ├── getQuestionByKeyword.js │ │ ├── getPlanQuestionList.js │ │ ├── getStudyPlanList.js │ │ ├── getQuestionCodeList.js │ │ ├── getQuestionToday.js │ │ ├── getQuestionRandom.js │ │ ├── getQuestionById.js │ │ ├── getAllQuestionList.js │ │ └── getQuestionTypes.js │ ├── http │ │ ├── urlJoin.js │ │ ├── graphql.js │ │ ├── submit.js │ │ └── fetch_.js │ ├── update │ │ ├── updateByEnv.js │ │ └── update.js │ ├── functions │ │ ├── removeDomTags.js │ │ ├── isSameData.js │ │ └── sizeUtil.js │ ├── loading │ │ └── loading.js │ └── logger │ │ └── logger.js ├── constants │ ├── date.const.js │ ├── question.const.js │ └── manager.const.js ├── view │ ├── language.view.js │ ├── update.view.js │ ├── create.view.js │ ├── check.view.js │ └── finder.view.js └── structures │ ├── ListNode.js │ ├── TreeNode.js │ └── Node.js ├── .commitlintrc.cjs ├── .prettierignore ├── scripts ├── loading.js └── create-color-font.js ├── jsconfig.json ├── .github ├── pull_request_template.md ├── workflows │ ├── ci.yml │ ├── release-cli.yml │ ├── contributors.yml │ └── pr-add-label.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml ├── CONTRIBUTING_CN.md └── CONTRIBUTING.md ├── eslint.config.js ├── .prettierrc ├── .eslintrc.js ├── SECURITY.md ├── .gitignore ├── .release-it.json ├── test ├── graph.spec.js ├── listNode.spec.js ├── tree.spec.js ├── setDataStructure.spec.js ├── create.spec.js └── paseDataStructure.spec.js ├── LICENSE ├── TO-DO.md ├── bin ├── lf.js ├── lc.js └── lk.js ├── package.json ├── esbuild.config.js ├── CODE_OF_CONDUCT.md ├── README_CN.md ├── README_JP.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .tgz 2 | httpData/ 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | # todo-highlight 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | common/template/* 2 | src/* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit 2 | -------------------------------------------------------------------------------- /resources/text/aim.js: -------------------------------------------------------------------------------- 1 | export const aim = `☕️ Enjoy your time on coding.` 2 | -------------------------------------------------------------------------------- /common/origin/readme.md: -------------------------------------------------------------------------------- 1 | # Origin 2 | 3 | > 吮指原味鸡~ 4 | > 5 | > 在这个目录中的都会按目录输出. 6 | -------------------------------------------------------------------------------- /resources/text/love.js: -------------------------------------------------------------------------------- 1 | export const love = `Made with ❤️ by EternalHeart team.` 2 | -------------------------------------------------------------------------------- /.commitlintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /resources/images/lc-cli-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/lc-cli-h.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* 10 | -------------------------------------------------------------------------------- /resources/images/create-finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/create-finish.png -------------------------------------------------------------------------------- /resources/images/template-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/template-page.png -------------------------------------------------------------------------------- /resources/images/service-qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/service-qrcode.jpg -------------------------------------------------------------------------------- /scripts/loading.js: -------------------------------------------------------------------------------- 1 | import ora from 'ora' 2 | 3 | const loading = ora('LP!给我加载!!!!').start() 4 | setTimeout(() => loading.stop(), 300000) 5 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionUrl.js: -------------------------------------------------------------------------------- 1 | export function getQuestionUrl(slug) { 2 | return `https://leetcode.cn/problems/${slug}/` 3 | } 4 | -------------------------------------------------------------------------------- /resources/images/fulfill-template-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/fulfill-template-new.png -------------------------------------------------------------------------------- /resources/images/create-new-by-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/create-new-by-template.png -------------------------------------------------------------------------------- /resources/images/generating-your-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EternalHeartTeam/leetcode-practice/HEAD/resources/images/generating-your-template.png -------------------------------------------------------------------------------- /common/utils/etc/willUse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 无用之用:标记可能会用的变量 3 | * @returns {*} 4 | * @param args 5 | */ 6 | export function willUse(...args) { 7 | return args 8 | } 9 | -------------------------------------------------------------------------------- /common/utils/file/parseFilePath.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | export function parseFilePath(oldPath) { 4 | return `\"${path.normalize(oldPath)}\"` 5 | } 6 | -------------------------------------------------------------------------------- /resources/template/template.js: -------------------------------------------------------------------------------- 1 | // 模板 - 生成的内容会被替换 2 | export const template = `@Title 3 | @Describe 4 | @Function 5 | 6 | @TestCase 7 | 8 | @Console 9 | ` 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "#common/*": ["./common/*"], 6 | "#resources/*": ["./resources/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /common/utils/file/getDirname.js: -------------------------------------------------------------------------------- 1 | import { dirname } from 'node:path' 2 | import { fileURLToPath } from 'node:url' 3 | 4 | const __filename = fileURLToPath(import.meta.url) 5 | export const __dirname = dirname(__filename) 6 | -------------------------------------------------------------------------------- /common/utils/file/getFileListBySameName.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'node:fs' 2 | 3 | export function getFileListBySameName(dir, name) { 4 | return readdirSync(dir).filter(filename => filename.includes(name)) 5 | } 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Git Commit Message Convention 2 | 3 | > This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). 4 | -------------------------------------------------------------------------------- /common/constants/date.const.js: -------------------------------------------------------------------------------- 1 | // 以毫秒计算的时间单位 2 | export const Second = 1000 3 | export const Minute = 60 * Second 4 | export const Hour = 60 * Minute 5 | export const Day = 24 * Hour 6 | export const Week = 7 * Day 7 | export const Year = 365 * Day 8 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionChineseName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 拼接中文标题 3 | * @param question 4 | * @returns {`${string}.${string}`} 5 | */ 6 | export function getQuestionChineseName(question) { 7 | return `${question.id}.${question.title}` 8 | } 9 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionFileName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取题目的路径 3 | * @param question 4 | * @returns {string} 5 | */ 6 | export function getQuestionFileName(question) { 7 | if (!question || !question?.id) 8 | return '' 9 | return `${question.id}.${question.slug}` 10 | } 11 | -------------------------------------------------------------------------------- /common/utils/cli-utils/referMode.js: -------------------------------------------------------------------------------- 1 | // 推测模式 2 | export function referMode(args, opts) { 3 | if (args.length > 0 || opts.identity) 4 | return 'identity' 5 | 6 | if (opts.random) 7 | return 'random' 8 | 9 | if (opts.all) 10 | return 'all' 11 | 12 | return 'today' 13 | } 14 | -------------------------------------------------------------------------------- /common/utils/cli-utils/getArgs.js: -------------------------------------------------------------------------------- 1 | // 获取模式对应的参数 2 | export function getArgs(mode, cmdArgs, cmdOpts) { 3 | switch (mode) { 4 | case 'identity': 5 | return cmdArgs.length ? cmdArgs.join(' ') : cmdOpts?.identity 6 | case 'random': 7 | case 'today': 8 | default: 9 | return null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/utils/etc/typeof_.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 修复null的类型检测 3 | * @param data 4 | * @returns {"undefined"|"object"|"boolean"|"number"|"string"|"function"|"symbol"|"bigint"|string} 5 | * @private 6 | */ 7 | export function typeof_(data) { 8 | if (data === null) 9 | return 'null' 10 | else return typeof data 11 | } 12 | -------------------------------------------------------------------------------- /common/utils/file/getCountBySameName.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'node:fs' 2 | 3 | /** 4 | * 根据指定的目录和文件名 给出存在的数量 5 | * @param dir 6 | * @param name 7 | * @returns {number} 8 | */ 9 | export function getCountBySameName(dir, name) { 10 | return readdirSync(dir).filter(filename => filename.includes(name)).length 11 | } 12 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu({ 4 | rules: { 5 | 'node/prefer-global/process': 'off', 6 | 'no-console': 'off', 7 | 'no-irregular-whitespace': 'off', 8 | 'eslint-comments/no-unlimited-disable': 'off', 9 | 'no-cond-assign': 'off', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /resources/text/description.js: -------------------------------------------------------------------------------- 1 | import { GITHUB_HOST, PackageName } from '#common/constants/question.const.js' 2 | 3 | export const description = ` 4 | A powerful practice platform for leetcode. 5 | CLI / Template Project / Plugin, you can create question by any way you like. 6 | See https://github.com/${GITHUB_HOST}/${PackageName} 7 | ` 8 | -------------------------------------------------------------------------------- /common/utils/file/getRootPath.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { currentEnv } from '#common/utils/etc/checkEnv.js' 3 | import { __dirname } from '#common/utils/file/getDirname.js' 4 | 5 | // 在cli环境下 执行目录为 bin 目录 根目录就是上一层目录 6 | export const rootPath = currentEnv() === 'project' ? path.dirname(path.dirname(path.dirname(__dirname))) : path.dirname(__dirname) 7 | -------------------------------------------------------------------------------- /common/utils/store/schemas/store.js: -------------------------------------------------------------------------------- 1 | import Realm from 'realm' 2 | 3 | export class Store extends Realm.Object { 4 | static schema = { 5 | name: 'Store', 6 | properties: { 7 | key: 'string', 8 | value: 'string', 9 | timestamp: { type: 'date', default: () => new Date() }, 10 | }, 11 | primaryKey: 'key', 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionLanguageList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionLanguageListJson } from '#resources/headers/questionLanguageListJson.js' 3 | 4 | export async function getQuestionLanguageList() { 5 | const res = await graphql(getQuestionLanguageListJson()) 6 | return res?.data.languageList 7 | } 8 | -------------------------------------------------------------------------------- /resources/headers/allQuestionRequestUrlJson.js: -------------------------------------------------------------------------------- 1 | export function getAllQuestionRequestUrlJson() { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: '{"operationName":"allQuestionUrls","variables":{},"query":"query allQuestionUrls {\\n allQuestionUrls {\\n questionUrl\\n __typename\\n }\\n}\\n"}', 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /common/utils/http/urlJoin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 拼接域名和地址 3 | * @param host 4 | * @param rest 5 | * @returns {string} 6 | */ 7 | export function url_join(host, ...rest) { 8 | const host_base = host.replace(/\/$/, '') 9 | const path = rest 10 | .join('/') 11 | .replace(/(\/){1,3}/g, '/') 12 | .replace(/^\//, '') 13 | return [host_base, path].join('/') 14 | } 15 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionListJson } from '#resources/headers/questionListJson.js' 3 | 4 | export async function getQuestionList() { 5 | const base = await graphql(getQuestionListJson()) 6 | // todo 列表 7 | const question = base.questions 8 | return question 9 | } 10 | -------------------------------------------------------------------------------- /scripts/create-color-font.js: -------------------------------------------------------------------------------- 1 | import { createColorFont } from '#common/utils/etc/createColorFont.js' 2 | import inquirer from 'inquirer' 3 | 4 | const question = [ 5 | { 6 | type: 'input', 7 | name: 'font', 8 | message: '请输入要渐变色的文本:', 9 | }, 10 | ] 11 | // 第一个问题 选择的模式 12 | const { font } = await inquirer.prompt(question, null) 13 | createColorFont(font) 14 | -------------------------------------------------------------------------------- /common/utils/update/updateByEnv.js: -------------------------------------------------------------------------------- 1 | import { exec } from 'node:child_process' 2 | 3 | /** 4 | * 更新CLI 5 | * @returns {Promise} 6 | */ 7 | export function updateCli() { 8 | return new Promise((resolve, reject) => { 9 | exec(`npm install -g leetcode-practice`, (err) => { 10 | if (err) 11 | reject(err) 12 | else resolve() 13 | }) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /common/utils/functions/removeDomTags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 去除dom标签 3 | * @param input 4 | * @returns {string|string} 5 | */ 6 | export function removeDomTags(input) { 7 | return input 8 | ?.replace(/<[^>]*>/g, '') 9 | .replaceAll(' ', ' ') 10 | .replaceAll(' ', ' ') 11 | .replaceAll('<', '<') 12 | .replaceAll('>', '>') 13 | .replaceAll('`', '') 14 | } 15 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionTagType.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionTagTypeJson } from '#resources/headers/questionTagTypeJson.js' 3 | 4 | export async function getQuestionTagType() { 5 | const { data } = await graphql(getQuestionTagTypeJson()) 6 | const { questionTagTypeWithTags } = data 7 | return questionTagTypeWithTags 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "tabWidth": 4, 5 | "trailingComma": "none", 6 | "printWidth": 360, 7 | "quoteProps": "consistent", 8 | "bracketSpacing": true, 9 | "bracketSameLine": true, 10 | "arrowParens": "always", 11 | "htmlWhitespaceSensitivity": "css", 12 | "endOfLine": "lf", 13 | "singleAttributePerLine": true 14 | } 15 | -------------------------------------------------------------------------------- /common/utils/http/graphql.js: -------------------------------------------------------------------------------- 1 | import { fetch_ } from '#common/utils/http/fetch_.js' 2 | import { url_join } from '#common/utils/http/urlJoin.js' 3 | 4 | /** 5 | * 请求 graphql 6 | * @param options 7 | * @param host 8 | * @returns {Promise} 9 | */ 10 | export async function graphql(options, host = 'https://leetcode.cn/') { 11 | return await fetch_(url_join(host, 'graphql'), options) 12 | } 13 | -------------------------------------------------------------------------------- /common/utils/http/submit.js: -------------------------------------------------------------------------------- 1 | import { fetch_ } from '#common/utils/http/fetch_.js' 2 | import { url_join } from '#common/utils/http/urlJoin.js' 3 | 4 | /** 5 | * 请求 submit 提交 6 | * @param options 7 | * @param host 8 | * @returns {Promise} 9 | */ 10 | export async function submit(options, host = 'https://leetcode.cn/') { 11 | return await fetch_(url_join(host, 'submit'), options) 12 | } 13 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionByKeyword.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionSearchJson } from '#resources/headers/questionSearchJson.js' 3 | 4 | export async function getQuestionByKeyword(keyword) { 5 | const questionData = await graphql(getQuestionSearchJson(keyword.toString())) 6 | return questionData?.data?.problemsetQuestionList?.questions 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Use Node.js 20.x 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: 20.x 16 | 17 | - name: ci 18 | run: | 19 | npm install 20 | npm run test 21 | -------------------------------------------------------------------------------- /common/utils/question-getter/getPlanQuestionList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getPlanQuestionListJson } from '#resources/headers/planQuestionListJson.js' 3 | 4 | export async function getPlanQuestionList(slug) { 5 | const res = await graphql(getPlanQuestionListJson(slug)) 6 | const { 7 | data: { studyPlanV2Detail }, 8 | } = res 9 | return studyPlanV2Detail 10 | } 11 | -------------------------------------------------------------------------------- /common/utils/file/getFilePathById.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | /** 4 | * 根据id获取文件的具体路径 5 | * @param id 6 | * @param baseDir 7 | * @returns {string|undefined|string[]} 8 | */ 9 | export function getFilePathById(id, baseDir = process.cwd()) { 10 | const dir = fs.readdirSync(baseDir) 11 | const files = dir.filter(o => o.startsWith(`${id}.`)) 12 | if (files.length > 1) 13 | return files 14 | return files?.[0] 15 | } 16 | -------------------------------------------------------------------------------- /common/utils/question-getter/getStudyPlanList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getStudyPlanListJson } from '#resources/headers/studyPlanListJson.js' 3 | 4 | export async function getStudyPlanList(type) { 5 | const res = await graphql(getStudyPlanListJson(type)) 6 | const { 7 | data: { 8 | studyPlansV2ByCatalog: { studyPlans }, 9 | }, 10 | } = res 11 | return studyPlans 12 | } 13 | -------------------------------------------------------------------------------- /common/utils/http/fetch_.js: -------------------------------------------------------------------------------- 1 | import ora from 'ora' 2 | 3 | /** 4 | * 基础请求-直接返回JSON格式的值 5 | * @param url 6 | * @param options {RequestInit&{loadText?:string}} 7 | * @returns {Promise} 8 | * @private 9 | */ 10 | export async function fetch_(url, options) { 11 | const loader = ora(options.loadText ?? 'loading...').start() 12 | const resp = await fetch(url, options).then(res => res.json()) 13 | loader.stop() 14 | return resp 15 | } 16 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionCodeList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionCodeListJson } from '#resources/headers/questionCodeListJson.js' 3 | 4 | /** 5 | * 获取代码列表 6 | * @param slug 7 | * @returns {Promise<*>} 8 | */ 9 | export async function getQuestionCodeList(slug) { 10 | const res = await graphql(getQuestionCodeListJson(slug)) 11 | return res.data.question?.codeSnippets 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es2021: true, 6 | }, 7 | extends: '@antfu', 8 | overrides: [ 9 | { 10 | env: { 11 | node: true, 12 | }, 13 | files: ['.eslintrc.{js,cjs}'], 14 | parserOptions: { 15 | sourceType: 'script', 16 | }, 17 | }, 18 | ], 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | }, 22 | rules: {}, 23 | } 24 | -------------------------------------------------------------------------------- /common/utils/file/getQuestionInDir.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | /** 5 | * 在目录中查找题目文件 6 | * @param dir 7 | * @returns {string|string[]} 8 | */ 9 | export function getQuestionFileInDir(dir) { 10 | const list = fs.readdirSync(dir) 11 | const questionLikes = list.filter(name => name.startsWith('question')).map(file => path.resolve(dir, file)) 12 | return questionLikes?.length === 1 ? questionLikes[0] : questionLikes 13 | } 14 | -------------------------------------------------------------------------------- /common/utils/question-handler/createMarkdown.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | /** 5 | * 创建markdown 6 | * @param {*} description 7 | * @param {*} questionPath 8 | */ 9 | export function createMarkdown(description, questionPath) { 10 | if (!description) 11 | return 12 | const dir = path.dirname(questionPath) 13 | const descriptionPath = path.join(dir, 'description.md') 14 | fs.writeFileSync(descriptionPath, description) 15 | } 16 | -------------------------------------------------------------------------------- /resources/headers/questionDetailJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionDetailJson(slug) { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: `{"query":"\\n query questionTranslations($titleSlug: String!) {\\n question(titleSlug: $titleSlug) {questionId\\n translatedTitle\\n translatedContent\\n jsonExampleTestcases\\n exampleTestcases\\n }\\n}\\n ","variables":{"titleSlug":"${slug}"},"operationName":"questionTranslations"}`, 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /resources/headers/questionTagTypeJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionTagTypeJson() { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: '{"query":"\\n query questionTagTypeWithTags {\\n questionTagTypeWithTags {\\n name\\n transName\\n tagRelation {\\n questionNum\\n tag {\\n name\\n id\\n nameTranslated\\n slug\\n }\\n }\\n }\\n}\\n ","variables":{},"operationName":"questionTagTypeWithTags"}', 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /common/utils/question-handler/getConsoleText.js: -------------------------------------------------------------------------------- 1 | import { DefaultLang } from '#common/constants/question.const.js' 2 | import { getQuestionUrl } from '#common/utils/question-handler/getQuestionUrl.js' 3 | import { setLineComment } from '#common/utils/question-handler/questionLanguage.js' 4 | 5 | export function getConsoleText(question) { 6 | const url = getQuestionUrl(question.slug) 7 | if (question.lang === DefaultLang) 8 | return `console.log('点击跳转到题目提交: ${url}');` 9 | else return setLineComment(question.lang, `题目地址:${url}`) 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Feature request(新需求)' 3 | about: 'Suggest an idea for this project' 4 | title: '[Feature Request] say something' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Background 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Proposal 14 | 15 | Describe the solution you'd like, better to provide some pseudo code. 16 | 17 | ## Additional context 18 | 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /resources/headers/questionLanguageListJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取当前语言列表 3 | * @returns {{headers: {"content-type": string}, method: string, body: string}} 4 | */ 5 | export function getQuestionLanguageListJson() { 6 | return { 7 | headers: { 'content-type': 'application/json' }, 8 | body: `{ 9 | "query": "\\n query languageList {\\n languageList {\\n id\\n name\\n }\\n}\\n ", 10 | "variables": {}, 11 | "operationName": "languageList" 12 | }`, 13 | method: 'POST', 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /resources/text/art-font-logo.js: -------------------------------------------------------------------------------- 1 | export const artFontLogo = ` __ _ _ ___ _ _ 2 | / / ___ ___| |_ ___ ___ __| | ___ / _ \\_ __ __ _ ___| |_(_) ___ ___ 3 | / / / _ \\/ _ \\ __/ __/ _ \\ / _\` |/ _ \\ / /_)/ '__/ _\` |/ __| __| |/ __/ _ \\ 4 | / /__| __/ __/ || (_| (_) | (_| | __/ / ___/| | | (_| | (__| |_| | (_| __/ 5 | \\____/\\___|\\___|\\__\\___\\___/ \\__,_|\\___| \\/ |_| \\__,_|\\___|\\__|_|\\___\\___| 6 | ` 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: npm # See documentation for possible values 9 | directory: / # Location of package manifests 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /common/utils/etc/checkEnv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 检测当前的环境是 cli 还是 项目中 3 | * 通过meta信息中的url来判断 4 | * cli: file:///Users/mac-106/wh131462/workspace/leetcode-practice/pl-cli/.bin/lc.js 5 | * project: file:///Users/mac-106/wh131462/workspace/leetcode-practice/common/utils/etc/checkEnv.js 6 | */ 7 | export function currentEnv() { 8 | const url = import.meta.url 9 | const projectReg = /etc\/checkEnv.js$/im 10 | return projectReg.test(url) ? 'project' : 'cli' 11 | } 12 | 13 | /** 14 | * 检查npm安装时的位置 15 | */ 16 | export function npmEnv() { 17 | return true ? 'global' : 'module' 18 | } 19 | -------------------------------------------------------------------------------- /resources/headers/questionCodeListJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionCodeListJson(slug) { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: `{"query":"\\n query questionEditorData($titleSlug: String!) {\\n question(titleSlug: $titleSlug) {\\n questionId\\n questionFrontendId\\n codeSnippets {\\n lang\\n langSlug\\n code\\n }\\n envInfo\\n enableRunCode\\n hasFrontendPreview\\n frontendPreviews\\n }\\n}\\n ","variables":{"titleSlug":"${slug}"},"operationName":"questionEditorData"}`, 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /resources/headers/questionTypeJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionTypesJson() { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: `{ 5 | "query": "\\n query questionTagTypeWithTags {\\n questionTagTypeWithTags {\\n name\\n transName\\n tagRelation {\\n questionNum\\n tag {\\n name\\n id\\n nameTranslated\\n slug\\n }\\n }\\n }\\n}\\n ", 6 | "variables": {}, 7 | "operationName": "questionTagTypeWithTags" 8 | }`, 9 | method: 'POST', 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionToday.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionTodayJson } from '#resources/headers/questionTodayJson.js' 3 | import { getQuestionDetail } from '../question-handler/getQuestionDetail.js' 4 | 5 | export async function getQuestionToday() { 6 | const question = await graphql(getQuestionTodayJson()) 7 | const today = question.data.todayRecord[0].question 8 | const { date } = question.data.todayRecord[0] 9 | const questionInfo = await getQuestionDetail(today.titleSlug, { date }) 10 | return questionInfo 11 | } 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | -------- | ------------------ | 10 | | v1.0.9-x | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Use this section to tell people how to report a vulnerability. 15 | 16 | Tell them where to go, how often they can expect to get an update on a 17 | reported vulnerability, what to expect if the vulnerability is accepted or 18 | declined, etc. 19 | -------------------------------------------------------------------------------- /common/utils/etc/createColorFont.js: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'node:fs' 2 | import path from 'node:path' 3 | import gradient_string from 'gradient-string' 4 | 5 | // 创建渐变色字体 6 | export function createColorFont(font) { 7 | const code = gradient_string([ 8 | { color: '#ff0000', pos: 0 }, 9 | { color: '#ffc600', pos: 0.5 }, 10 | { color: '#003dff', pos: 1 }, 11 | ])(font) 12 | writeFileSync(path.resolve(process.cwd(), 'colorFont.js'), code) 13 | console.log(`[ColorFont]Create color font: ${font}\ncode location:${path.resolve(process.cwd(), 'colorFont.js')}`) 14 | console.log(code) 15 | } 16 | -------------------------------------------------------------------------------- /resources/headers/codeSubmitJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 提交请求的JSON数据 3 | * 地址:https://leetcode.cn/problems/reverse-integer/submit/ 4 | * @param lang 5 | * @param id 6 | * @param code 7 | * @returns {{headers: {"content-type": string}, method: string, body: string}} 8 | */ 9 | export function codeSubmitJson(lang, id, code) { 10 | return { 11 | headers: { 'content-type': 'application/json' }, 12 | body: `{ 13 | "lang": "${lang}", 14 | "question_id": "${id}", 15 | "typed_code": "${code}" 16 | }`, 17 | method: 'POST', 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/utils/etc/open.js: -------------------------------------------------------------------------------- 1 | import { exec } from 'node:child_process' 2 | import os from 'node:os' 3 | import { logger } from '#common/utils/logger/logger.js' 4 | 5 | const platform = os.platform() 6 | 7 | /** 8 | * 打开浏览器 9 | * @param {string} url 10 | */ 11 | export function open(url) { 12 | switch (platform) { 13 | case 'darwin': 14 | exec(`open "${url}"`) 15 | break 16 | case 'win32': 17 | exec(`start "${url}"`) 18 | break 19 | case 'linux': 20 | exec(`xdg-open "${url}"`) 21 | break 22 | default: 23 | logger.info(`Unsupported platform: ${platform}`) 24 | break 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/headers/planQuestionListJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取hot 100 的请求header 3 | * @returns {{headers: {"content-type": string}, method: string, body: string}} 4 | */ 5 | export function getPlanQuestionListJson(slug) { 6 | return { 7 | headers: { 'content-type': 'application/json' }, 8 | body: `{"query":"\\n query studyPlanPastSolved($slug: String!) {\\n studyPlanV2Detail(planSlug: $slug) {\\n planSubGroups {\\n slug\\n questions {\\n titleSlug\\n translatedTitle\\n status\\n }\\n }\\n }\\n}\\n ","variables":{"slug":"${slug}"},"operationName":"studyPlanPastSolved"}`, 9 | method: 'POST', 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /resources/headers/questionListJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionListJson(skip = 0, limit = 50) { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: `{"query":"query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) {\\nproblemsetQuestionList(\\ncategorySlug: $categorySlug\\nlimit: $limit\\nskip: $skip\\nfilters: $filters) {\\ntotal\\nquestions {\\n frontendQuestionId\\n title\\n titleCn\\n titleSlug\\n}\\n}\\n}","variables":{"categorySlug":"all-code-essentials","skip":${skip},"limit":${limit},"filters":{}},"operationName":"problemsetQuestionList"}`, 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/release-cli.yml: -------------------------------------------------------------------------------- 1 | name: Create Cli Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'cli-v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | if: github.repository == 'EternalHeartTeam/leetcode-practice' 12 | steps: 13 | - uses: actions/checkout@v3 14 | # Setup .npmrc file to publish to npm 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '20' 18 | registry-url: 'https://registry.npmjs.org' 19 | - run: npm install 20 | - run: npm run build-cli 21 | - run: npm run publish-cli 22 | env: 23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json* 8 | yarn-lock.json* 9 | pnpm-debug.log* 10 | lerna-debug.log* 11 | pnpm-lock.yaml 12 | node_modules 13 | dist 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | coverage 29 | coverage/** 30 | src/* 31 | 32 | # store file 33 | resources/stores/* 34 | 35 | # cli-dist 36 | pl-cli 37 | */colorFont.js 38 | pl-build 39 | 40 | # package manager 41 | yarn.lock 42 | package-lock.json 43 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionRandom.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getRandomId } from '#common/utils/question-handler/getRandomId.js' 3 | import { getQuestionSearchJson } from '#resources/headers/questionSearchJson.js' 4 | import { getQuestionDetail } from '../question-handler/getQuestionDetail.js' 5 | 6 | export async function getQuestionRandom() { 7 | const id = await getRandomId() 8 | const base = await graphql(getQuestionSearchJson(id.toString())) 9 | const slug = base.data.problemsetQuestionList.questions.find(o => o.frontendQuestionId === id.toString()).titleSlug 10 | const question = await getQuestionDetail(slug) 11 | return question 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug report(缺陷问题反馈)' 3 | about: 'Report a bug to help us improve' 4 | title: '[Bug] say something' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 12 | 13 | ## What happens? 14 | 15 | 16 | 17 | 18 | 19 | 20 | ## How To Reproduce 21 | 22 | **Steps to reproduce the behavior:** 1. 2. 23 | 24 | **Expected behavior** 1. 2. 25 | 26 | 27 | 28 | ## Context 29 | 30 | - **leetcode-practice Version**: 31 | - **Node Version**: 32 | - **Platform**: 33 | -------------------------------------------------------------------------------- /common/utils/store/schemas/question.js: -------------------------------------------------------------------------------- 1 | import Realm from 'realm' 2 | 3 | export class Question extends Realm.Object { 4 | static schema = { 5 | name: 'Question', 6 | properties: { 7 | _id: { type: 'objectId', default: () => new Realm.BSON.ObjectId() }, 8 | id: 'string', 9 | mode: 'string', 10 | slug: 'string?', 11 | title: 'string?', 12 | detail: 'string?', 13 | jsonExampleTestcases: 'string?', 14 | exampleTestcases: 'string?', 15 | lang: 'string', 16 | code: 'string?', 17 | url: 'string?', 18 | date: 'string?', 19 | timestamp: { type: 'date', default: () => new Date() }, 20 | }, 21 | primaryKey: '_id', 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionById.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionSearchJson } from '#resources/headers/questionSearchJson.js' 3 | import { getQuestionDetail } from '../question-handler/getQuestionDetail.js' 4 | 5 | export async function getQuestionById(id) { 6 | const base = await graphql(getQuestionSearchJson(id.toString())) 7 | const questionContent = base?.data?.problemsetQuestionList?.questions?.find(o => o?.frontendQuestionId === id.toString()) 8 | if (!questionContent) { 9 | return { 10 | id: null, 11 | } 12 | } 13 | const slug = questionContent.titleSlug 14 | const question = await getQuestionDetail(slug, { id }) 15 | return question 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/contributors.yml: -------------------------------------------------------------------------------- 1 | name: gen-org-contributors 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | generate: 10 | runs-on: ubuntu-latest 11 | if: github.repository == 'EternalHeartTeam/leetcode-practice' 12 | steps: 13 | - uses: thinkasany/organize-contributors@master 14 | with: 15 | organize_name: EternalHeartTeam 16 | github_token: ${{ secrets.ACTION_TOKEN }} 17 | png_path: images/contributors.png 18 | json_path: contributors.json 19 | branch: svg 20 | commit_message: 'chore: update contributors' 21 | excludes_list: 'ImgBotApp,github-actions[bot],actions-user,imgbot[bot],dependabot[bot]' 22 | -------------------------------------------------------------------------------- /.github/workflows/pr-add-label.yml: -------------------------------------------------------------------------------- 1 | name: pr-add-label 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | jobs: 8 | add-label: 9 | permissions: 10 | contents: read 11 | pull-requests: write 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check PR number 15 | id: pr_number 16 | run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV 17 | 18 | - name: Run add-label Action 19 | uses: thinkasany/pr-label-action@master 20 | with: 21 | github_token: ${{ secrets.ACTION_TOKEN }} 22 | pr_number: ${{ env.PR_NUMBER }} 23 | organize_name: EternalHeartTeam 24 | team_name: eternalheartteam 25 | -------------------------------------------------------------------------------- /common/utils/question-handler/checkQuestionByPath.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import vm from 'node:vm' 3 | import { showLogs } from '#common/utils/question-handler/showLogs.js' 4 | 5 | /** 6 | * 执行脚本 - 可传入上下文 7 | * @param filePath 8 | * @param context 9 | * @returns {any} 10 | */ 11 | export function executeScript(filePath, context) { 12 | const fileContent = fs.readFileSync(filePath, 'utf-8') 13 | const script = new vm.Script(fileContent) 14 | return script.runInContext(context) 15 | } 16 | 17 | /** 18 | * 执行问题检测进程 19 | * @param path 20 | */ 21 | export async function checkQuestionByPath(path) { 22 | return await executeScript( 23 | path, 24 | vm.createContext({ 25 | showLogs, 26 | console, 27 | }), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /common/utils/store/schemas/allQuestion.js: -------------------------------------------------------------------------------- 1 | import Realm from 'realm' 2 | 3 | export class AllQuestion extends Realm.Object { 4 | static schema = { 5 | name: 'AllQuestion', 6 | properties: { 7 | questionId: 'string', 8 | questionFrontendId: 'string?', 9 | questionType: 'string?', 10 | categoryTitle: 'string?', 11 | title: 'string?', 12 | titleSlug: 'string?', 13 | difficulty: 'string?', 14 | isPaidOnly: 'bool?', 15 | codeSnippets: 'string?', 16 | topicTags: 'string?', 17 | relatedTags: 'string?', 18 | translatedTitle: 'string?', 19 | stats: 'string?', 20 | extra: 'string?', 21 | isNewQuestion: 'bool?', 22 | frequency: 'string?', 23 | }, 24 | primaryKey: 'questionId', 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/constants/question.const.js: -------------------------------------------------------------------------------- 1 | // region 项目相关 2 | // 默认语言 3 | export const DefaultLang = 'javascript' 4 | // 默认版本号 5 | export const DefaultVer = '0.0.0' 6 | // 包名 7 | export const PackageName = 'leetcode-practice' 8 | // github 主账号 9 | export const GITHUB_HOST = 'EternalHeartTeam' 10 | // endregion 11 | // region 域名列表 12 | // npm 主域名 13 | export const NPM_URL = 'https://npmjs.org/' 14 | // npm 仓库域名 15 | export const NPM_REGISTRY_URL = 'https://registry.npmjs.org/' 16 | 17 | // github raw 主域名 18 | export const GITHUB_RAW = 'https://raw.githubusercontent.com/' 19 | // github 20 | export const GITHUB_URL = 'https://github.com/' 21 | // gitee 22 | export const GITEE_URL = 'https://gitee.com/' 23 | // endregion 24 | // region 实用变量 25 | // 默认请求数据数量限制 26 | export const DefaultLimit = 50 27 | // endregion 28 | -------------------------------------------------------------------------------- /common/view/language.view.js: -------------------------------------------------------------------------------- 1 | import { DefaultLang } from '#common/constants/question.const.js' 2 | import { logger } from '#common/utils/logger/logger.js' 3 | import { LANGUAGES, setQuestionLanguage } from '#common/utils/question-handler/questionLanguage.js' 4 | import inquirer from 'inquirer' 5 | 6 | export async function easyLanguageView(defaultLang = DefaultLang) { 7 | const list = LANGUAGES.map(o => o.name) 8 | const setQuestion = [ 9 | { 10 | type: 'list', 11 | name: 'newSet', 12 | message: '请确认你要设置CLI的语言环境(如果选项匹配成功,那么按下回车确认即可)', 13 | choices: list, 14 | default: defaultLang, 15 | }, 16 | ] 17 | const { newSet } = await inquirer.prompt(setQuestion, null) 18 | logger.info('设置语言环境为:', newSet) 19 | await setQuestionLanguage(newSet) 20 | process.exit(0) 21 | } 22 | -------------------------------------------------------------------------------- /resources/headers/studyPlanListJson.js: -------------------------------------------------------------------------------- 1 | // 'sprint-interview-company' // 名企面试 · 突击备战 2 | // 'cracking-coding-interview' // 面试准备 · 全面通关 3 | // 'deep-dive-topics' // 专项计划 · 深入学习 4 | export function getStudyPlanListJson(type) { 5 | return { 6 | headers: { 'content-type': 'application/json' }, 7 | body: `{\"query\":\"\\n query GetStudyPlanByCatalog($catalogSlug: String!, $offset: Int!, $limit: Int!) {\\n studyPlansV2ByCatalog(catalogSlug: $catalogSlug, offset: $offset, limit: $limit) {\\n hasMore\\n total\\n studyPlans {\\n slug\\n questionNum\\n premiumOnly\\n onGoing\\n name\\n highlight\\n cover\\n }\\n }\\n}\\n \",\"variables\":{\"offset\":0,\"catalogSlug\":\"${type}\",\"limit\":12},\"operationName\":\"GetStudyPlanByCatalog\"}`, 8 | method: 'POST', 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/utils/question-getter/getAllQuestionList.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getAllQuestionRequestUrlJson } from '#resources/headers/allQuestionRequestUrlJson.js' 3 | import ora from 'ora' 4 | 5 | export async function getAllQuestionUrl() { 6 | const { data } = await graphql(getAllQuestionRequestUrlJson()) 7 | const { allQuestionUrls } = data 8 | const { questionUrl } = allQuestionUrls 9 | return questionUrl 10 | } 11 | 12 | export async function getAllQuestionList() { 13 | const url = await getAllQuestionUrl() 14 | const loader = ora('loading...').start() 15 | const allQuestionData = await fetch(url, { 16 | headers: { 'content-type': 'application/json' }, 17 | body: null, 18 | method: 'GET', 19 | }) 20 | loader.stop() 21 | return allQuestionData.json() 22 | } 23 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "release": true, 4 | "web": true, 5 | "autoGenerate": true 6 | }, 7 | "git": { 8 | "commitMessage": "chore: release v${version}", 9 | "tagName": "cli-v${version}", 10 | "tag": true, 11 | "push": true, 12 | "pushArgs": ["--follow-tags"] 13 | }, 14 | "npm": { 15 | "publish": false 16 | }, 17 | "hooks": { 18 | "before:release": "prettier --write CHANGELOG.md && git add CHANGELOG.md ", 19 | "after:release": "git checkout master && git push origin master:stable-v${version} || true && echo 更新版本 ${version} 成功!" 20 | }, 21 | "plugins": { 22 | "@release-it/bumper": { 23 | "preset": "angular" 24 | }, 25 | "@release-it/conventional-changelog": { 26 | "preset": "angular", 27 | "infile": "CHANGELOG.md", 28 | "ignoreRecommendedBump": true 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/utils/functions/isSameData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 是否为相同数据 3 | * @param a 4 | * @param b 5 | * @returns {this is string[]|boolean} 6 | */ 7 | export function isSameData(a, b) { 8 | const typeA = typeof a 9 | const typeB = typeof b 10 | if (typeA !== typeB) 11 | return false 12 | switch (typeA) { 13 | case 'bigint': 14 | case 'boolean': 15 | case 'number': 16 | case 'string': 17 | case 'symbol': 18 | case 'undefined': 19 | return a === b 20 | case 'function': 21 | return a.toString() === b.toString() 22 | case 'object': { 23 | if (a === null || a === undefined) 24 | return a === b 25 | 26 | const keysA = Object.keys(a) 27 | const keysB = Object.keys(b) 28 | if (keysA.length !== keysB.length) 29 | return false 30 | return keysA.every(key => isSameData(a[key], b[key])) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/structures/ListNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ListNode 链表数据结构 3 | * @param val 4 | * @param next 5 | * @constructor 6 | */ 7 | 8 | export class ListNode { 9 | constructor(val, next) { 10 | this.val = val === undefined ? 0 : val 11 | this.next = next === undefined ? null : next 12 | } 13 | 14 | static parse(arr) { 15 | if (arr.length === 0) 16 | return null // Return null for an empty array 17 | 18 | const head = new ListNode(arr.shift(), null) 19 | let current = head 20 | while (arr.length > 0) { 21 | current.next = new ListNode(arr.shift(), null) 22 | current = current.next 23 | } 24 | return head 25 | } 26 | 27 | static toArray(listNodes, arr = []) { 28 | if (listNodes === undefined || listNodes === null) 29 | return arr 30 | 31 | arr.push(listNodes.val) 32 | return ListNode.toArray(listNodes.next, arr) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionIdBySlug.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | 3 | const headers = { 4 | 'content-type': 'application/json', 5 | } 6 | 7 | export async function getQuestionIdBySlug(titleSlug) { 8 | const body = { 9 | query: '\n query questionTitle($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n title\n titleSlug\n isPaidOnly\n difficulty\n likes\n dislikes\n categoryTitle\n }\n}\n ', 10 | variables: { 11 | titleSlug, 12 | }, 13 | operationName: 'questionTitle', 14 | } 15 | const initJson = { 16 | headers, 17 | body: JSON.stringify(body), 18 | method: 'POST', 19 | } 20 | const res = await graphql(initJson) 21 | const { data: question } = res 22 | return question 23 | } 24 | 25 | // await getQuestionIdBySlug("group-anagrams") 26 | -------------------------------------------------------------------------------- /common/utils/etc/checkVisionInfo.js: -------------------------------------------------------------------------------- 1 | import { DefaultVer, GITHUB_HOST, GITHUB_URL, NPM_URL, PackageName } from '#common/constants/question.const.js' 2 | import { rootPath } from '#common/utils/file/getRootPath.js' 3 | import { url_join } from '#common/utils/http/urlJoin.js' 4 | import { logger } from '#common/utils/logger/logger.js' 5 | import { getQuestionLanguage } from '#common/utils/question-handler/questionLanguage.js' 6 | import { aim } from '#resources/text/aim.js' 7 | 8 | /** 9 | * 检查版本信息 加一些额外输出 10 | */ 11 | export async function checkVisionInfo() { 12 | const version = process.env.VERSION ?? DefaultVer 13 | const location = rootPath 14 | const lang = await getQuestionLanguage() 15 | logger.info(`version: ${version}\nlanguage: ${lang}\nlocation: file://${location}\ngithub: ${url_join(GITHUB_URL, GITHUB_HOST, PackageName)}\nnpm: ${url_join(NPM_URL, 'package', PackageName)}\n\n${aim}`) 16 | } 17 | -------------------------------------------------------------------------------- /resources/headers/questionTodayJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionTodayJson() { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: '{"query":"\\n query questionOfToday {\\n todayRecord {\\n date\\n userStatus\\n question {\\n questionId\\n frontendQuestionId: questionFrontendId\\n difficulty\\n title\\n titleCn: translatedTitle\\n titleSlug\\n paidOnly: isPaidOnly\\n freqBar\\n isFavor\\n acRate\\n status\\n solutionNum\\n hasVideoSolution\\n topicTags {\\n name\\n nameTranslated: translatedName\\n id\\n }\\n extra {\\n topCompanyTags {\\n imgUrl\\n slug\\n numSubscribed\\n }\\n }\\n }\\n lastSubmission {\\n id\\n }\\n }\\n}\\n ","variables":{},"operationName":"questionOfToday"}', 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /common/utils/loading/loading.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | 3 | export class Loading { 4 | text = 'loading...' 5 | constructor(text) { 6 | this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] 7 | this.currentFrame = 0 8 | this.interval = null 9 | this.text = text 10 | } 11 | 12 | start() { 13 | if (this.interval) 14 | clearInterval(this.interval) 15 | this.interval = setInterval(() => { 16 | process.stdout.write(chalk.blueBright(`\r${this.frames[this.currentFrame]} ${this.text}`)) 17 | 18 | this.currentFrame++ 19 | if (this.currentFrame === this.frames.length) 20 | this.currentFrame = 0 21 | }, 80) 22 | return this 23 | } 24 | 25 | stop() { 26 | clearInterval(this.interval) 27 | process.stdout.write('\r') // 清除动画最后一帧 28 | return this 29 | } 30 | 31 | // 在发生异常时调用此方法清除动画 32 | handleException() { 33 | this.stop() 34 | return this 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/utils/question-getter/getQuestionTypes.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getQuestionTypesJson } from '#resources/headers/questionTypeJson.js' 3 | 4 | /** 5 | * 获取问题的类型 6 | * 数据结构: 7 | * tags = Array; 8 | * interface Category{ 9 | * name:string;// 目录名 英文 10 | * transName:string;// 目录名 中文 11 | * tagRelation:Array; //关系 相当于子列表 12 | * } 13 | * interface TagRelation{ 14 | * questionNum:number;// 关联问题数量 15 | * tag:Tag; // 具体标签信息 16 | * } 17 | * interface Tag{ 18 | * name:string; // 标签名字 英文 19 | * id:string; // 标签的id 20 | * nameTranslated:string; // 标签名字 中文 21 | * slug:string; // 标签的标识符 核心字段 筛选可用 22 | * } 23 | * @returns {Promise<*>} 24 | */ 25 | export async function getQuestionTypes() { 26 | const res = await graphql(getQuestionTypesJson()) 27 | const tags = res.data?.questionTagTypeWithTags 28 | // logger.info(JSON.stringify(tags)) 29 | return tags 30 | } 31 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING_CN.md: -------------------------------------------------------------------------------- 1 | **中文** · [English](./CONTRIBUTING.md) 2 | 3 | # 贡献者 4 | 5 | 如果你也想参与我们的项目共建,请详细阅读以下开发约定,只有大家拥有一个共识,我们的项目才会越来越好! 6 | 7 | ## 贡献代码 8 | 9 | ### 1. 分支管理 10 | 11 | 不要在`master`分支进行开发, `master`分支只进行`pr合并`与`发布版本`, 如果是日常开发, 请在`dev`分支进行开发, 如果是`新特性`开发,请创建特性分支`feat-xxx`,同理,`修复bug`, 请创建修复分支`fix-xxx`. 12 | 13 | ### 2. 提交流程 14 | 15 | 在`dev`分支或者`feat-xxx`分支`开发完成`并`提交commit`之后, 请使用`git rebase origin/master`进行`本地合并`,在本地解决完成所有的代码冲突之后,再进行`pr请求`,`发送请求`到有权限的成员,会及时进行合并. 16 | 17 | ## 3. 关于特性分支和dev分支 18 | 19 | 默认`特性分支`和`修复分支`提交`pr`合并到`master`之后,会进行`删除`分支. 20 | 21 | `dev`分支不会进行删除,但是在其上开发的时候,请先进行`git rebase master`同步主分支(因为`主分支`接受`特性分支`和`修复分支`的`合并`,所以会出现`master分支`早于`dev分支`的情况). 22 | 23 | ## 4. rebase同步操作说明 24 | 25 | 在进行对`主仓库的同步`的时候,使用`rebase`解决完成`冲突`之后,使用`git status`你会发现出现`pull [数字]`和`push [数字]`标识,其含义为`落后`和`领先`远程分支的记录数, 这种是`rebase`产生的`正常情况`,在这个时候需要执行`git push -f`对远端进行`强制推送`,实现`同步`master分支,就不会产生多余的commit了. 26 | 27 | ## 参考信息 28 | 29 | 参考链接: [分支管理](https://www.ruanyifeng.com/blog/2012/07/git.html) 30 | -------------------------------------------------------------------------------- /common/utils/functions/sizeUtil.js: -------------------------------------------------------------------------------- 1 | // 千 2 | export const KB = 1024 3 | // 兆 4 | export const MB = 1024 * KB 5 | // 吉 6 | export const GB = 1024 * MB 7 | // 太 8 | export const TB = 1024 * GB 9 | // 拍 10 | export const PB = 1024 * GB 11 | 12 | /** 13 | * 获取文件的单位 14 | * @param size 15 | * @return {size: number, label: string} 16 | */ 17 | export function getFileSizeUnit(size) { 18 | if (size < KB) 19 | return { size: 1, label: 'B' } 20 | if (size < MB) 21 | return { size: KB, label: 'KB' } 22 | if (size < GB) 23 | return { size: MB, label: 'MB' } 24 | if (size < TB) 25 | return { size: GB, label: 'GB' } 26 | if (size < PB) 27 | return { size: TB, label: 'TB' } 28 | 29 | return { size: PB, label: 'PB' } 30 | } 31 | /** 32 | * 获取文件的尺寸 33 | * @param size 文件大小 34 | * @param precision 小数位 35 | */ 36 | export function getFileSize(size, precision = 2) { 37 | const fileSizeType = getFileSizeUnit(size) 38 | return `${(size / fileSizeType.size).toFixed(precision)} ${fileSizeType.label}` 39 | } 40 | -------------------------------------------------------------------------------- /common/constants/manager.const.js: -------------------------------------------------------------------------------- 1 | /** 2 | * npm 的安装指令 3 | * @param packageName 4 | * @param isUpdate 5 | * @param isGlobal 6 | * @returns {string} 7 | * @constructor 8 | */ 9 | export function NpmInstall(packageName, isUpdate, isGlobal) { 10 | return `npm ${isUpdate ? 'update' : 'install'} ${isGlobal ? '-g' : ''} ${packageName}` 11 | } 12 | 13 | /** 14 | * @description yarn 的安装指令 15 | * @param packageName 16 | * @param isUpdate 17 | * @param isGlobal 18 | * @returns {string} 19 | * @constructor 20 | */ 21 | export function YarnInstall(packageName, isUpdate, isGlobal) { 22 | return `yarn ${isGlobal ? 'global' : ''} ${isUpdate ? 'upgrade' : 'add'} ${packageName}` 23 | } 24 | 25 | /** 26 | * @description pnpm 的安装指令 27 | * @param packageName 28 | * @param isUpdate 29 | * @param isGlobal 30 | * @returns {string} 31 | * @constructor 32 | */ 33 | export function PnpmInstall(packageName, isUpdate, isGlobal) { 34 | return `pnpm ${isGlobal ? 'global' : ''} ${isUpdate ? 'update' : 'install'} ${packageName}` 35 | } 36 | -------------------------------------------------------------------------------- /test/graph.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { expect, it } from 'vitest'; 3 | import { Node } from '#common/structures/Node'; 4 | 5 | const graphArray1 = [ 6 | [2, 4], 7 | [1, 3], 8 | [2, 4], 9 | [1, 3] 10 | ]; 11 | const graphArray2 = [[]]; 12 | const graphArray3 = []; 13 | const graphArray4 = [[2], [1]]; 14 | const graphArray5 = [ 15 | [2, 5], 16 | [1, 3, 5], 17 | [2, 4], 18 | [3, 5], 19 | [1, 2, 4] 20 | ]; 21 | const graphArray6 = [ 22 | [2, 5], 23 | [1, 3], 24 | [2, 4], 25 | [3, 5], 26 | [4, 1] 27 | ]; 28 | const graphArray7 = [ 29 | [2, 4], 30 | [1, 3], 31 | [2, 4], 32 | [3, 1] 33 | ]; 34 | const graphArray8 = [[2, 3, 4, 5], [1, 3, 4], [1, 2], [1, 2], [1]]; 35 | const max = 9; 36 | 37 | it('测试无向连通图', () => { 38 | for (let i = 1; i < max; i++) { 39 | const currentArray = `graphArray${[i]}`; 40 | const graph = Node.parse(eval(currentArray)); 41 | expect(eval(currentArray)).toEqual(Node.toArray(graph)); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /common/utils/question-handler/createQuestionCopy.js: -------------------------------------------------------------------------------- 1 | // todo 创建问题副本 2 | import path from 'node:path' 3 | import { getCountBySameName } from '#common/utils/file/getCountBySameName.js' 4 | import { createQuestionFile } from '#common/utils/question-handler/createQuestion.js' 5 | import { getQuestionFileExtension } from '#common/utils/question-handler/questionLanguage.js' 6 | 7 | /** 8 | * 创建副本 9 | * @param question 10 | * @param questionDir 11 | * @returns {Promise} 12 | */ 13 | export function createQuestionCopy(question, questionDir) { 14 | if (!question || !question.id) 15 | return Promise.reject(new Error('question is empty')) 16 | const dir = path.dirname(questionDir) 17 | const name = `${question.id}.${question.slug}` 18 | const affix = ` [${getCountBySameName(dir, name)}]` 19 | const copyFileDir = path.join(dir, `${name}${affix}`) 20 | const copyFilePath = path.join(copyFileDir, `question${getQuestionFileExtension(question.lang)}`) 21 | return createQuestionFile(copyFileDir, copyFilePath, question) 22 | } 23 | -------------------------------------------------------------------------------- /common/utils/cli-utils/createQuestion.js: -------------------------------------------------------------------------------- 1 | import { create } from '#common/utils/cli-utils/create.js' 2 | import { logger } from '#common/utils/logger/logger.js' 3 | import { getQuestionById } from '#common/utils/question-getter/getQuestionById.js' 4 | import { getQuestionIdBySlug } from '#common/utils/question-handler/getQuestionIdBySlug.js' 5 | 6 | /** 7 | * 通过指定的titleSlug创建题目 8 | * @param titleSlug 9 | * @param baseDir 10 | * @returns {Promise} 11 | */ 12 | export async function createQuestionByTitleSlug(titleSlug, baseDir = process.cwd()) { 13 | const { question } = await getQuestionIdBySlug(titleSlug) 14 | await createQuestionById(question.questionId, baseDir) 15 | } 16 | 17 | /** 18 | * 通过id创建题目 19 | * @param id 20 | * @param baseDir 21 | * @returns {Promise} 22 | */ 23 | export async function createQuestionById(id, baseDir = process.cwd()) { 24 | const question = await getQuestionById(id) 25 | if (!question?.id) 26 | logger.warn(`指定编号: [ ${id} ] 题目不存在.`) 27 | await create('identity', question, baseDir) 28 | } 29 | -------------------------------------------------------------------------------- /test/listNode.spec.js: -------------------------------------------------------------------------------- 1 | import { ListNode } from '#common/structures/ListNode' 2 | import { expect, it } from 'vitest' 3 | 4 | it('toArray 正常数组', () => { 5 | // 创建链表 6 | const head = new ListNode(1) 7 | const node1 = new ListNode(2) 8 | const node2 = new ListNode(3) 9 | head.next = node1 10 | node1.next = node2 11 | 12 | const arr = ListNode.toArray(head) 13 | expect(arr).toEqual([1, 2, 3]) 14 | }) 15 | it('toArray undefined', () => { 16 | const arr = ListNode.toArray(undefined) 17 | expect(arr).toEqual([]) 18 | }) 19 | it('toArray false', () => { 20 | const arr = ListNode.toArray(false) 21 | expect(arr).toEqual([undefined]) 22 | }) 23 | it('toArray 1', () => { 24 | const arr = ListNode.toArray(1) 25 | expect(arr).toEqual([undefined]) 26 | }) 27 | it('parse [1,2,3]', () => { 28 | const listNode = ListNode.parse([1, 2, 3]) 29 | expect(listNode.val).toEqual(1) 30 | expect(listNode.next?.val).toEqual(2) 31 | expect(listNode.next?.next?.val).toEqual(3) 32 | }) 33 | it('parse []', () => { 34 | const listNode = ListNode.parse([]) 35 | expect(listNode).toEqual(null) 36 | }) 37 | -------------------------------------------------------------------------------- /resources/headers/questionSearchJson.js: -------------------------------------------------------------------------------- 1 | export function getQuestionSearchJson(keyword) { 2 | return { 3 | headers: { 'content-type': 'application/json' }, 4 | body: `{"query":"\\n query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) {\\n problemsetQuestionList(\\n categorySlug: $categorySlug\\n limit: $limit\\n skip: $skip\\n filters: $filters\\n ) {\\n hasMore\\n total\\n questions {\\n acRate\\n difficulty\\n freqBar\\n frontendQuestionId\\n isFavor\\n paidOnly\\n solutionNum\\n status\\n title\\n titleCn\\n titleSlug\\n topicTags {\\n name\\n nameTranslated\\n id\\n slug\\n }\\n extra {\\n hasVideoSolution\\n topCompanyTags {\\n imgUrl\\n slug\\n numSubscribed\\n }\\n }\\n }\\n }\\n}\\n ","variables":{"categorySlug":"all-code-essentials","skip":0,"limit":50,"filters":{"searchKeywords":"${keyword}"}},"operationName":"problemsetQuestionList"}`, 5 | method: 'POST', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /common/utils/file/getLineNumberByContent.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { logger } from '#common/utils/logger/logger.js' 3 | 4 | /** 5 | * 通过给入的文件地址和内容 给出对应的行号 6 | * @param filePath 7 | * @param searchString 8 | * @returns {Promise} 9 | */ 10 | export function getLineNumberByContent(filePath, searchString) { 11 | return new Promise((resolve) => { 12 | let lineNumber = 0 13 | const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' }) 14 | 15 | readStream.on('data', (chunk) => { 16 | const lines = chunk.split('\n') 17 | for (const line of lines) { 18 | lineNumber++ 19 | if (line.includes(searchString)) { 20 | readStream.close() 21 | resolve(lineNumber) 22 | return 23 | } 24 | } 25 | }) 26 | 27 | readStream.on('end', () => { 28 | logger.warn(`[WARN] "${searchString}" not found in file: ${filePath}`) 29 | resolve(0) 30 | }) 31 | 32 | readStream.on('error', () => { 33 | logger.warn(`[WARN] "${searchString}" not found in file: ${filePath}`) 34 | resolve(0) 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /common/origin/checkUpdate.js: -------------------------------------------------------------------------------- 1 | import { Day } from '#common/constants/date.const.js' 2 | import { NpmInstall, PnpmInstall, YarnInstall } from '#common/constants/manager.const.js' 3 | import { PackageName } from '#common/constants/question.const.js' 4 | import { logger } from '#common/utils/logger/logger.js' 5 | import { getStore, setStore } from '#common/utils/store/controller/store.js' 6 | import { checkUpdate } from '#common/utils/update/update.js' 7 | 8 | const { timestamp = 0, hasShow = false } = (await getStore('checkResult')) ?? {} 9 | if (Date.now() - timestamp <= Day || hasShow) 10 | process.exit(0) 11 | 12 | const { localVersion, npmVersion, isCliUpdate } = await checkUpdate() 13 | const needShow = false 14 | if (isCliUpdate) { 15 | const installInfo = [NpmInstall, YarnInstall, PnpmInstall] 16 | .map(fun => fun(PackageName, true, true)) // 暂时先默认为全局 17 | .join('\n') 18 | logger.warn(`[leetcode-practice] 检测到新版本[ ${npmVersion} ]已经发布! 您当前的版本为[ ${localVersion} ]! 您可以执行对应的指令进行手动更新~`) 19 | logger.info(`${installInfo}`) 20 | } 21 | await setStore('checkResult', { timestamp: Date.now(), hasShow: needShow }) 22 | process.exit(0) 23 | -------------------------------------------------------------------------------- /common/view/update.view.js: -------------------------------------------------------------------------------- 1 | import { logger } from '#common/utils/logger/logger.js' 2 | import { checkUpdate } from '#common/utils/update/update.js' 3 | import { updateCli } from '#common/utils/update/updateByEnv.js' 4 | import inquirer from 'inquirer' 5 | 6 | export async function easyUpdateView() { 7 | const { localVersion = '未检出', npmVersion = '未检出', isCliUpdate } = await checkUpdate() 8 | logger.info(`当前版本:[ ${localVersion} ] npm包最新版本:[ ${npmVersion} ]`) 9 | // 3. 询问是否更新 10 | if (isCliUpdate) { 11 | const checkQuestion = { 12 | type: 'confirm', 13 | name: 'willUpdate', 14 | message: `检测到可更新版本[ ${npmVersion} ],是否进行更新?`, 15 | } 16 | const { willUpdate } = await inquirer.prompt(checkQuestion, null) 17 | if (willUpdate) { 18 | // 4.1 选择更新 19 | logger.info('开始更新...') 20 | await updateCli() 21 | logger.info('更新完成~祝你使用愉快~') 22 | } 23 | else { 24 | // 4.2 取消更新 25 | logger.info('你选择跳过此次更新,如果想要进行更新,随时可以使用参数 -u 进行更新检测!祝你使用愉快~') 26 | } 27 | process.exit(0) 28 | } 29 | else { 30 | logger.info('当前已是最新版本!祝你使用愉快~') 31 | process.exit(0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 EternalHeart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionDetail.js: -------------------------------------------------------------------------------- 1 | import { graphql } from '#common/utils/http/graphql.js' 2 | import { getCodeBySlug } from '#common/utils/question-handler/code.js' 3 | import { getQuestionLanguage } from '#common/utils/question-handler/questionLanguage.js' 4 | import { getQuestionDetailJson } from '#resources/headers/questionDetailJson.js' 5 | 6 | /** 7 | * 获取代码详情 8 | * @param slug 9 | * @param extra 10 | * @returns {Promise<*&{id:*,slug:*, title: *,detail: *, lang:*,code: *,jsonExampleTestcases:*,exampleTestcases:*}>} 11 | */ 12 | export async function getQuestionDetail(slug, extra = {}) { 13 | // 标题的英文字符串 14 | const questionDetail = await graphql(getQuestionDetailJson(slug)) 15 | const detail = questionDetail.data.question 16 | const curLang = await getQuestionLanguage() 17 | const code = await getCodeBySlug(slug, curLang) 18 | return { 19 | id: detail?.questionId, 20 | slug, 21 | title: detail?.translatedTitle, 22 | detail: detail?.translatedContent, 23 | lang: curLang, 24 | code, 25 | jsonExampleTestcases: detail?.jsonExampleTestcases, 26 | exampleTestcases: detail?.exampleTestcases, 27 | ...extra, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/tree.spec.js: -------------------------------------------------------------------------------- 1 | import { TreeNode } from '#common/structures/TreeNode' 2 | import { expect, it } from 'vitest' 3 | 4 | const mockTree = [1, 2, 3] 5 | const mockTree2 = [1, null, 2] 6 | const mockTree3 = [3, 9, 20, null, null, 15, 7] 7 | it('测试树', () => { 8 | const node = TreeNode.parse(mockTree) 9 | const result = TreeNode.toArray(node) 10 | 11 | const node2 = TreeNode.parse(mockTree2) 12 | const result2 = TreeNode.toArray(node2) 13 | expect(result2).toEqual(mockTree2) 14 | expect(result).toEqual(mockTree) 15 | }) 16 | it('测试树 toArray特殊值', () => { 17 | expect(TreeNode.toArray(null)).toEqual([]) 18 | }) 19 | 20 | it('测试树 parse 特殊值', () => { 21 | expect(TreeNode.parse([])).toBeNull() 22 | }) 23 | it('测试树 parse mockTree', () => { 24 | expect(TreeNode.parse(mockTree)).toEqual(new TreeNode(1, new TreeNode(2), new TreeNode(3))) 25 | }) 26 | it('测试树 pase mockTree2', () => { 27 | expect(TreeNode.parse(mockTree2)).toEqual(new TreeNode(1, null, new TreeNode(2))) 28 | }) 29 | it('测试树 pase mockTree3', () => { 30 | expect(TreeNode.parse(mockTree3)).toEqual(new TreeNode(3, new TreeNode(9, null), new TreeNode(20, new TreeNode(15, null), new TreeNode(7)))) 31 | }) 32 | it('测试树 pase mockTree4', () => { 33 | expect(TreeNode.parse([0])).toEqual(new TreeNode()) 34 | }) 35 | -------------------------------------------------------------------------------- /common/utils/logger/logger.js: -------------------------------------------------------------------------------- 1 | import { getStore } from '#common/utils/store/controller/store.js' 2 | import chalk from 'chalk' 3 | 4 | class LOGGER { 5 | isOn = true 6 | constructor(_env) { 7 | // console.log( 8 | // chalk.bgGray(`[logger init] The current env is ${env ?? 'not plugin'}.`) 9 | // ) 10 | } 11 | 12 | /** 13 | * 开启 14 | */ 15 | on() { 16 | this.isOn = true 17 | } 18 | 19 | /** 20 | * 关闭 21 | */ 22 | off() { 23 | this.isOn = false 24 | } 25 | 26 | get forbidden() { 27 | return !this.isOn 28 | } 29 | 30 | /** 31 | * 普通消息 32 | * @param message{*} 33 | * @param args{*[]} 34 | */ 35 | info(message, ...args) { 36 | if (this.forbidden) 37 | return 38 | console.log(chalk.whiteBright(message, ...args)) 39 | } 40 | 41 | /** 42 | * 警告 43 | * @param message{*} 44 | * @param args{*[]} 45 | */ 46 | warn(message, ...args) { 47 | if (this.forbidden) 48 | return 49 | console.log(chalk.yellowBright(message, ...args)) 50 | } 51 | 52 | /** 53 | * 错误信息 54 | * @param message{*} 55 | * @param args{*[]} 56 | */ 57 | error(message, ...args) { 58 | if (this.forbidden) 59 | return 60 | console.log(chalk.redBright(message, ...args)) 61 | } 62 | } 63 | const { env = null } = (await getStore('config')) ?? {} 64 | export const logger = new LOGGER(env) 65 | -------------------------------------------------------------------------------- /common/structures/TreeNode.js: -------------------------------------------------------------------------------- 1 | export class TreeNode { 2 | constructor(val, left, right) { 3 | this.left = null 4 | this.val = val === undefined ? 0 : val 5 | this.left = left === undefined ? null : left 6 | this.right = right === undefined ? null : right 7 | } 8 | 9 | static parse(arr) { 10 | if (arr.length === 0) 11 | return null 12 | const root = new TreeNode(arr[0]) 13 | const queue = [root] 14 | for (let i = 1; i < arr.length; i += 2) { 15 | const node = queue.shift() 16 | if (arr[i] !== null) { 17 | node.left = new TreeNode(arr[i]) 18 | queue.push(node.left) 19 | } 20 | if (arr[i + 1] !== null) { 21 | node.right = new TreeNode(arr[i + 1]) 22 | queue.push(node.right) 23 | } 24 | } 25 | return root 26 | } 27 | 28 | static toArray(treeNode) { 29 | const result = [] 30 | if (!treeNode) 31 | return result 32 | 33 | const queue = [treeNode] 34 | 35 | while (queue.length > 0) { 36 | const node = queue.shift() 37 | if (node) { 38 | result.push(node.val) 39 | queue.push(node.left) 40 | queue.push(node.right) 41 | } 42 | else { 43 | result.push(null) 44 | } 45 | } 46 | 47 | while (result.length > 0 && result[result.length - 1] === null) result.pop() 48 | 49 | return result 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/utils/store/controller/question.js: -------------------------------------------------------------------------------- 1 | import { exeOnce } from '#common/utils/store/store-realm.js' 2 | 3 | /** 4 | * 根据模式读取对象 5 | * @param mode 6 | * @returns {unknown} 7 | */ 8 | export function getQuestionByMode(mode) { 9 | return exeOnce((realm) => { 10 | const all = realm.objects('Question') 11 | const question = all.filtered('mode=$0', mode)?.[0] 12 | return question?.toJSON() 13 | }) 14 | } 15 | 16 | /** 17 | * 存对象 18 | * @param mode 19 | * @param question 20 | * @returns {*} 21 | */ 22 | export function setQuestion(mode, question) { 23 | return exeOnce((realm) => { 24 | let newQuestion 25 | realm.write(() => { 26 | realm.delete(realm.objects('Question').filtered('mode=$0', mode)) 27 | newQuestion = realm.create('Question', Object.assign(question, { mode })) 28 | }) 29 | return newQuestion.toJSON() 30 | }) 31 | } 32 | 33 | /** 34 | * 删除某一个模式 35 | * @param mode 36 | */ 37 | export function deleteQuestionByMode(mode) { 38 | return exeOnce((realm) => { 39 | realm.write(() => { 40 | const modes = realm.objects('Question').filtered('mode=$0', mode) 41 | realm.delete(modes) 42 | }) 43 | }) 44 | } 45 | 46 | /** 47 | * 删除全部 48 | */ 49 | export function deleteAllQuestion() { 50 | return exeOnce((realm) => { 51 | realm.write(() => { 52 | realm.delete(realm.objects('Question')) 53 | }) 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /common/utils/cli-utils/create.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { getLineNumberByContent } from '#common/utils/file/getLineNumberByContent.js' 3 | import { logger } from '#common/utils/logger/logger.js' 4 | import { createQuestion } from '#common/utils/question-handler/createQuestion.js' 5 | import { createQuestionCopy } from '#common/utils/question-handler/createQuestionCopy.js' 6 | import { getQuestionChineseName } from '#common/utils/question-handler/getQuestionChineseName.js' 7 | import { getQuestionFileName } from '#common/utils/question-handler/getQuestionFileName.js' 8 | import { setQuestion } from '#common/utils/store/controller/question.js' 9 | 10 | /** 11 | * 创建函数 12 | * @param mode 13 | * @param question 14 | * @param baseDir 15 | * @returns {Promise} 16 | */ 17 | export function create(mode, question, baseDir) { 18 | logger.info(`MODE: ${mode}`) 19 | return new Promise((resolve) => { 20 | setQuestion(mode, question) 21 | const questionDir = path.join(baseDir, getQuestionFileName(question)) 22 | createQuestion(question, questionDir).then(async (path) => { 23 | if (!path) 24 | path = await createQuestionCopy(question, questionDir) 25 | const line = (await getLineNumberByContent(path, '@QUESTION_START')) + 1 26 | logger.info(`题目[${getQuestionChineseName(question)}]获取成功!\n题目文件地址为:file://${path}:${line}`) 27 | resolve(true) 28 | }) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /TO-DO.md: -------------------------------------------------------------------------------- 1 | # TO-DO List 2 | 3 | ## 功能 4 | 5 | ### 优先级高 6 | 7 | - [ ] finder完成 8 | 1. 主要功能:可以对题目进行不同的筛选或者关键词搜索 9 | 1. 关键词搜索 - 通过关键词进行模糊搜索,通过选择列表项进行创建题目 10 | 2. 热门列表查看 - 查询hot100题目列表,可以通过左右按键分页查看 11 | 3. 筛选 12 | 1. 标签筛选 13 | 2. 难度筛选 14 | 3. 通过率 15 | - [ ] 登陆和提交到leetcode 16 | - [ ] 提供一个类似vue-cli-serve的脚手架配置项 17 | - [ ] 国际化 18 | - [ ] 插件制作-WS/VS code 19 | - [ ] fork脚本的编写 20 | 21 | ### 优先级中 22 | 23 | - [ ] 获取题解和代码 24 | - [ ] 获取更完备的测试用例,增强用户体验 25 | 26 | ### 优先级低 27 | 28 | ### 代码优化及基础建设 29 | 30 | - [ ] e2e测试集成和仿真环境搭建 31 | 32 | ## 已完归档 33 | 34 | - [x] 1.模板:获取js的函数体并替换生成文件中的@function 35 | - [x] 2.模板:从detail中获取输入用例的数据填充@Testcase 36 | - [x] 3.模板:获取跳转每日一题的链接替换@url 37 | - [x] 4.函数:优化时间和资源统计函数 38 | - [x] 5.优化创建时的体验,添加重复时候的确认覆盖或者添加额外符号 39 | - [x] 6.特殊数据结构的处理(链表ListNode,树TreeNode,无向连通图Node)的处理 40 | - [x] 7.创建某一特定编号的题目脚本,以及实现随机题目【随机题目汇集本地题目,然后排除自己本地存在的题目进行随机】 41 | - [x] 8.加入eslint 42 | - [x] 9.私人项目部署的实现方案 43 | - [x] 10.commonJS -> ES6 Module 44 | - [x] 11.实现在编辑器中预览图片(markdown中可以查看) 45 | - [x] 12.使用realm进行持久化,替换store.json 46 | - [x] 13.实现lk/lf/lc指令的封装 47 | - [x] 14.重构工具代码,对UI和logic进行解耦 48 | - [x] 15.封装npm包,方便后续做成编辑器插件 49 | - [x] 16.文档的编写 50 | - [x] 17.项目的升级检测以及升级脚本 51 | - [x] 18.指定编程语言代码获取 52 | - [x] 19.基础参数设置与缓存 53 | - [x] 20.添加语言的设定 54 | - [x] 21.store文件升级冲突引起的报错无感修复 55 | - [x] 22.优化随机题目的随机方式,减少请求 56 | - [x] 23.真随机一题实现 57 | -------------------------------------------------------------------------------- /common/structures/Node.js: -------------------------------------------------------------------------------- 1 | export class Node { 2 | constructor(val, neighbors) { 3 | this.val = val === undefined ? 0 : val 4 | this.neighbors = neighbors === undefined ? [] : neighbors 5 | } 6 | 7 | static parse(edges) { 8 | const nodeMap = new Map() 9 | 10 | // 创建节点 11 | const getNode = (val) => { 12 | if (!nodeMap.has(val)) { 13 | const newNode = new Node(val) 14 | nodeMap.set(val, newNode) 15 | } 16 | return nodeMap.get(val) 17 | } 18 | 19 | // 连接节点 20 | edges.forEach((neighbors, index) => { 21 | const val = index + 1 22 | const currentNode = getNode(val) 23 | neighbors.forEach((neighborVal) => { 24 | const neighborNode = getNode(neighborVal) 25 | currentNode.neighbors.push(neighborNode) 26 | }) 27 | }) 28 | 29 | return nodeMap.size > 0 ? nodeMap.values().next().value : null 30 | } 31 | 32 | static toArray(node) { 33 | if (!node) 34 | return [] 35 | 36 | const visited = new Set() 37 | const result = [] 38 | 39 | const dfs = (currentNode) => { 40 | if (visited.has(currentNode.val)) 41 | return 42 | 43 | const { neighbors, val } = currentNode 44 | visited.add(val) 45 | result.push(neighbors.map(({ val }) => val)) 46 | currentNode.neighbors.forEach((neighbor) => { 47 | dfs(neighbor) 48 | }) 49 | } 50 | 51 | dfs(node) 52 | 53 | return result 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bin/lf.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import { DefaultVer } from '#common/constants/question.const.js'; 3 | import { commonMode } from '#common/utils/cli-utils/commonMode.js'; 4 | import { willUse } from '#common/utils/etc/willUse.js'; 5 | import { easyFinderView } from '#common/view/finder.view.js'; 6 | import { aim } from '#resources/text/aim.js'; 7 | import { artFontLogo } from '#resources/text/art-font-logo.js'; 8 | import { description } from '#resources/text/description.js'; 9 | import { lfExamples } from '#resources/text/examples.js'; 10 | import { love } from '#resources/text/love.js'; 11 | import { program } from 'commander'; 12 | 13 | const version = process.env.VERSION ?? DefaultVer 14 | program 15 | .version(version) 16 | .description(`${description}\n${artFontLogo}\n${aim}`) 17 | .addHelpText('after', lfExamples + love) 18 | .option('-e, --easy', 'Use easy mode.') 19 | .option('-d, --directory ', 'Set the question directory.') 20 | .option('-l, --language [language]', 'Set/Get the code language of question.') 21 | .option('-v, --ver', 'Check the version info and some extra info about leetcode-practice.') 22 | .option('-u, --update', 'Check the version to determine whether to update to the latest one.') 23 | .parse(process.argv) 24 | 25 | const cmdArgs = program.args 26 | const cmdOpts = program.opts() 27 | // 通用参数执行 28 | const baseDir = await commonMode(cmdOpts, easyFinderView) 29 | await easyFinderView(baseDir) 30 | willUse(cmdArgs, baseDir) 31 | process.exit(0) 32 | -------------------------------------------------------------------------------- /common/utils/question-handler/getQuestionListCodeBy.js: -------------------------------------------------------------------------------- 1 | import { createQuestionById, createQuestionByTitleSlug } from '#common/utils/cli-utils/createQuestion.js' 2 | import { getPlanQuestionList } from '#common/utils/question-getter/getPlanQuestionList.js' 3 | 4 | // 根据 slug 获取创建promise列表 5 | async function createSlugPromiseList(slugList, baseDir = process.cwd()) { 6 | return slugList.map((titleSlug) => { 7 | return createQuestionByTitleSlug(titleSlug, baseDir) 8 | }) 9 | } 10 | 11 | // 根据 id 获取创建promise列表 12 | async function createIdPromiseList(questionList, baseDir = process.cwd()) { 13 | return questionList.map((question) => { 14 | return createQuestionById(question.questionId, baseDir) 15 | }) 16 | } 17 | 18 | /** 19 | * 創建題目列表通過plan slug 20 | * @param slug 21 | * @param baseDir 22 | */ 23 | export async function getQuestionListCodeBySlug(slug, baseDir) { 24 | const { planSubGroups } = await getPlanQuestionList(slug) 25 | const questionTitleList = planSubGroups.reduce((acc, cur) => { 26 | acc.push(...cur.questions.map(res => res.titleSlug)) 27 | return acc 28 | }, []) 29 | const promiseList = await createSlugPromiseList(questionTitleList, baseDir) 30 | return await Promise.allSettled(promiseList) 31 | } 32 | 33 | /** 34 | * 創建題目列表通過tag 35 | * @param tagQuestionList 36 | * @param baseDir 37 | */ 38 | export async function getQuestionListCodeByTag(tagQuestionList, baseDir) { 39 | const promiseList = await createIdPromiseList(tagQuestionList, baseDir) 40 | return await Promise.allSettled(promiseList) 41 | } 42 | -------------------------------------------------------------------------------- /common/utils/question-handler/createQuestion.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { getSupportCode } from '#common/utils/question-handler/code.js' 4 | import { fulfillQuestion } from '#common/utils/question-handler/fulfillQuestion.js' 5 | import { getQuestionFileExtension } from '#common/utils/question-handler/questionLanguage.js' 6 | 7 | /** 8 | * 创建问题 9 | * @param question 问题对象 10 | * @param questionDir 问题要创建的目录 截止到名字 11 | * @returns {Promise} 12 | */ 13 | export function createQuestion(question, questionDir) { 14 | return new Promise((resolve) => { 15 | const filePath = path.normalize(path.join(questionDir, `question${getQuestionFileExtension(question.lang)}`)) 16 | if (fs.existsSync(filePath)) { 17 | resolve(false) 18 | } 19 | else { 20 | createQuestionFile(questionDir, filePath, question) 21 | .then(path => resolve(path)) 22 | .catch(() => resolve(false)) 23 | } 24 | }) 25 | } 26 | export function createQuestionFile(questionDir, questionFilePath, question) { 27 | return new Promise((resolve, reject) => { 28 | try { 29 | // 创建题目的目录 30 | fs.mkdir(questionDir, { recursive: true }, async () => { 31 | // 写入文件和模板 32 | await fulfillQuestion(questionFilePath, question) 33 | if (!question.code) { 34 | const supports = await getSupportCode(question.slug) 35 | console.warn(`此题目不支持当前语言[${question.lang}]!受支持的语言有[${supports.join(',')}]!`) 36 | } 37 | resolve(questionFilePath) 38 | }) 39 | } 40 | catch (e) { 41 | reject(e) 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /common/utils/store/store-realm.js: -------------------------------------------------------------------------------- 1 | import { readdirSync, rmSync } from 'node:fs' 2 | import path from 'node:path' 3 | import { rootPath } from '#common/utils/file/getRootPath.js' 4 | import { AllQuestion } from '#common/utils/store/schemas/allQuestion.js' 5 | import { Question } from '#common/utils/store/schemas/question.js' 6 | import { Store } from '#common/utils/store/schemas/store.js' 7 | import Realm from 'realm' 8 | 9 | const localPath = path.resolve(rootPath, 'resources/stores/store.realm') 10 | /** 11 | * 开启 12 | * @returns {Promise} 13 | */ 14 | export async function open() { 15 | let realm 16 | try { 17 | realm = await Realm.open({ 18 | schema: [Question, AllQuestion, Store], 19 | path: localPath, 20 | }) 21 | } 22 | catch (e) { 23 | if (e?.message?.includes('Migration')) 24 | await cleanStore() 25 | 26 | realm = await Realm.open({ 27 | schema: [Question, AllQuestion, Store], 28 | path: localPath, 29 | }) 30 | } 31 | return realm 32 | } 33 | /** 34 | * 执行一次 35 | * @param callback 36 | * @returns {Promise} 37 | */ 38 | export async function exeOnce(callback) { 39 | const realm = await open() 40 | const res = await callback(realm) 41 | realm.close() 42 | return res 43 | } 44 | /** 45 | * 清理缓存 46 | * @returns {Promise} 47 | */ 48 | export function cleanStore() { 49 | return new Promise((resolve) => { 50 | const dir = path.dirname(localPath) 51 | const files = readdirSync(dir) 52 | files.forEach((file) => { 53 | rmSync(path.resolve(dir, file), { recursive: true, force: true }) 54 | }) 55 | resolve() 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /test/setDataStructure.spec.js: -------------------------------------------------------------------------------- 1 | import { getDataStructure } from '#common/utils/question-handler/parseStructure.js' 2 | import { expect, it } from 'vitest' 3 | 4 | const jsDoc = `/** 5 | * Definition for singly-linked list. 6 | * function ListNode(val, next) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.next = (next===undefined ? null : next) 9 | * } 10 | */ 11 | /** 12 | * @param {ListNode[]} lists 13 | * @return {ListNode} 14 | */ 15 | var mergeKLists = function(lists) { 16 | // lists = convertListNode(lists); 17 | console.log(lists, '111') 18 | // 当是空数组的情况下 19 | if (!lists.length) { 20 | return null; 21 | } 22 | // 合并两个排序链表 23 | const merge = (head1, head2) => { 24 | let dummy = new ListNode(0); 25 | let cur = dummy; 26 | // 新链表,新的值小就先接谁 27 | while (head1 && head2) { 28 | if (head1.val < head2.val) { 29 | cur.next = head1; 30 | head1 = head1.next; 31 | } else { 32 | cur.next = head2; 33 | head2 = head2.next; 34 | } 35 | cur = cur.next; 36 | } 37 | // 如果后面还有剩余的就把剩余的接上 38 | cur.next = head1 == null ? head2 : head1; 39 | return dummy.next; 40 | }; 41 | const mergeLists = (lists, start, end) => { 42 | if (start + 1 == end) { 43 | return lists[start]; 44 | } 45 | // 输入的k个排序链表,可以分成两部分,前k/2个链表和后k/2个链表 46 | // 如果将这前k/2个链表和后k/2个链表分别合并成两个排序的链表,再将两个排序的链表合并,那么所有链表都合并了 47 | let mid = (start + end) >> 1; 48 | let head1 = mergeLists(lists, start, mid); 49 | let head2 = mergeLists(lists, mid, end); 50 | return merge(head1, head2); 51 | }; 52 | return mergeLists(lists, 0, lists.length); 53 | };` 54 | 55 | it('测试', () => { 56 | const param = getDataStructure(jsDoc) 57 | const returnArray = getDataStructure(jsDoc, 'return') 58 | expect(param).toEqual(['ListNode[]']) 59 | expect(returnArray).toEqual(['ListNode']) 60 | }) 61 | -------------------------------------------------------------------------------- /common/utils/question-handler/parseStructure.js: -------------------------------------------------------------------------------- 1 | import { ListNode } from '#common/structures/ListNode.js' 2 | import { Node } from '../../structures/Node.js' 3 | import { TreeNode } from '../../structures/TreeNode.js' 4 | 5 | const paramMap = { 6 | // 入参map 7 | cases: { 8 | 'ListNode': _param => ListNode.parse(_param), 9 | 'ListNode[]': param => param.map(res => ListNode.parse(res)), 10 | 'TreeNode': param => TreeNode.parse(param), 11 | 'Node': param => Node.parse(param), 12 | 'default': param => param, 13 | }, 14 | // 返回值map 15 | return: { 16 | 'ListNode': param => ListNode.toArray(param), 17 | 'ListNode[]': param => param.map(res => ListNode.toArray(res)), 18 | 'TreeNode': param => TreeNode.toArray(param), 19 | 'Node': param => Node.toArray(param), 20 | 'default': param => param, 21 | }, 22 | } 23 | /** 24 | * 25 | * @param {Array} params 26 | * @param {string[]} structs 27 | * @param {string} type 28 | */ 29 | export function setDataStructure(params, structs, type = 'cases') { 30 | return params.map((param, index) => { 31 | const struct = structs[index] 32 | const map = paramMap[type] 33 | return map[struct] ? map[struct](param) : map.default(param) 34 | }) 35 | } 36 | 37 | /** 38 | * 获取test case 入参的数据类型 39 | * @param {string} code leetcode的实例函数体 40 | * @param {string} type 类型,param入参,returns返回值 41 | * @returns {string[]} 42 | */ 43 | export function getDataStructure(code, type = 'param') { 44 | const regexMap = { 45 | param: /@param\s+\{\s*([^}\s]+)\s*\}/g, 46 | return: /@return\s+\{\s*([^}\s]+)\s*\}/g, 47 | } 48 | const regex = regexMap[type] 49 | const paramTypes = [] 50 | let match 51 | while ((match = regex.exec(code)) !== null) paramTypes.push(match[1]) 52 | 53 | return paramTypes 54 | } 55 | -------------------------------------------------------------------------------- /common/utils/question-handler/code.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { DefaultLang } from '#common/constants/question.const.js' 3 | import { getQuestionCodeList } from '#common/utils/question-getter/getQuestionCodeList.js' 4 | import { getLangByExtension, setLineComment } from '#common/utils/question-handler/questionLanguage.js' 5 | 6 | /** 7 | * 获取代码 8 | * @param slug 9 | * @param lang 10 | * @returns {Promise<*>} 11 | */ 12 | export async function getCodeBySlug(slug, lang) { 13 | const list = await getQuestionCodeList(slug) 14 | return list?.find(o => o.langSlug === lang)?.code 15 | } 16 | /** 17 | * 获取支持的代码语言 18 | * @param slug 19 | * @returns {Promise} 20 | */ 21 | export async function getSupportCode(slug) { 22 | const list = (await getQuestionCodeList(slug)) ?? [] 23 | return list?.map(code => code?.langSlug) 24 | } 25 | 26 | /** 27 | * 生成有范围块的代码 28 | * @param lang 29 | * @param code 30 | * @returns {*|string} 31 | */ 32 | export function getCodeRange(lang, code) { 33 | if (!code) 34 | return setLineComment(lang, `!important: 此题目没有当前语言[${lang}]的代码模板!`) 35 | return `${setLineComment(lang, '@QUESTION_START') + code}\n${setLineComment(lang, '@QUESTION_END')}` 36 | } 37 | /** 38 | * 获取文件中的代码部分 39 | */ 40 | export function getCodeInFile(filePath) { 41 | const lang = getLangByExtension(filePath)?.lang ?? DefaultLang 42 | const data = fs.readFileSync(filePath, 'utf-8') 43 | const startTag = setLineComment(lang, '@QUESTION_START') 44 | const endTag = setLineComment(lang, '@QUESTION_END') 45 | const rangeReg = new RegExp(`${startTag}.*${endTag}`, 'ms') 46 | const rangeTagReg = new RegExp(`(${startTag}|${endTag})+`, 'gm') 47 | const match = data.match(rangeReg) 48 | if (!match) 49 | return null 50 | return match[0]?.replace(rangeTagReg, '') 51 | } 52 | -------------------------------------------------------------------------------- /common/utils/question-handler/fulfillQuestion.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { getCodeRange } from '#common/utils/question-handler/code.js' 3 | import { getConsoleText } from '#common/utils/question-handler/getConsoleText.js' 4 | import { getQuestionChineseName } from '#common/utils/question-handler/getQuestionChineseName.js' 5 | import { setBlockComment } from '#common/utils/question-handler/questionLanguage.js' 6 | import { template } from '#resources/template/template.js' 7 | import { removeDomTags } from '../functions/removeDomTags.js' 8 | import { createMarkdown } from './createMarkdown.js' 9 | import { getTestCase } from './getTestCase.js' 10 | 11 | /** 12 | * @typedef {object} Question 13 | * @property {string} title 14 | * @property {string} slug 15 | * @property {number} date 16 | * @property {string} detail 17 | * @property {string} lang 18 | * @property {string} code 19 | */ 20 | 21 | /** 22 | * @type {Question} 23 | */ 24 | 25 | /** 26 | * 27 | * @param {Question} question 28 | * 29 | */ 30 | export function generateTemplateContent(question) { 31 | const title = `${getQuestionChineseName(question)} ${question.date ? `[${question.date}]` : ''}\n` 32 | const describe = removeDomTags(question.detail)?.replace(/\n+/g, '\n') ?? '' 33 | const lang = question.lang 34 | const code = question.code 35 | return template 36 | .replace('@Title', setBlockComment(lang, title + describe)) 37 | .replace('@Describe', '') 38 | .replace('@Function', getCodeRange(lang, code)) 39 | .replace('@TestCase', getTestCase(question)) 40 | .replace('@Console', getConsoleText(question)) 41 | } 42 | /** 43 | * 模板文件内容替换并生成文件 44 | * @param questionPath 45 | * @param question 46 | */ 47 | export function fulfillQuestion(questionPath, question) { 48 | return new Promise((resolve) => { 49 | // 创建描述文件 md 50 | createMarkdown(question.detail, questionPath) 51 | // 开始填充内容 52 | const newData = generateTemplateContent(question) 53 | // 创建文件 54 | fs.writeFile(questionPath, newData, (err) => { 55 | if (err) 56 | throw err 57 | resolve() 58 | }) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [中文](./CONTRIBUTING_CN.md) · **English** 2 | 3 | # Contributor 4 | 5 | If you would like to contribute to our project, please carefully read the following development conventions. Only when we have a consensus can our project get better and better! 6 | 7 | ## Contributing Code 8 | 9 | ### 1. Branch Management 10 | 11 | Do not develop on the `master` branch. The `master` branch is only for merging pull requests and releasing versions. For daily development, please use the `dev` branch. For feature development, please create a feature branch named `feat-xxx`. Similarly, for bug fixes, please create a fix branch named `fix-xxx`. 12 | 13 | ### 2. Commit Process 14 | 15 | After completing development and committing on the `dev` branch or a `feat-xxx` branch, please use `git rebase origin/master` for local merging. After resolving all code conflicts locally, submit a pull request to members with permission for timely merging. 16 | 17 | ## 3. About Feature Branches and Dev Branch 18 | 19 | By default, after submitting a pull request (PR), feature branches and fix branches will be deleted upon merging into the `master` branch. 20 | 21 | The `dev` branch, however, will not be deleted. During development on this branch, please synchronize the main branch by performing `git rebase master` beforehand. This is because the `master` branch accepts merges from feature branches and fix branches, potentially causing the `master` branch to be ahead of the `dev` branch. 22 | 23 | ## 4. Rebase Synchronization Procedure 24 | 25 | When synchronizing with the main repository using `rebase` to resolve conflicts, after resolving conflicts, you may notice indicators such as `pull [number]` and `push [number]` when using `git status`. These indicate the number of commits behind and ahead of the remote branch, respectively. This is a normal situation resulting from `rebase`. At this point, you need to execute `git push -f` to perform a forced push to the remote branch, ensuring synchronization with the `master` branch and avoiding unnecessary commits. 26 | 27 | ## References 28 | 29 | Reference Link: [Branch Management](https://www.ruanyifeng.com/blog/2012/07/git.html) 30 | -------------------------------------------------------------------------------- /common/utils/cli-utils/commonMode.js: -------------------------------------------------------------------------------- 1 | import { fork } from 'node:child_process' 2 | import path from 'node:path' 3 | import { currentEnv } from '#common/utils/etc/checkEnv.js' 4 | import { checkVisionInfo } from '#common/utils/etc/checkVisionInfo.js' 5 | import { rootPath } from '#common/utils/file/getRootPath.js' 6 | import { logger } from '#common/utils/logger/logger.js' 7 | import { getQuestionLanguage } from '#common/utils/question-handler/questionLanguage.js' 8 | import { easyLanguageView } from '#common/view/language.view.js' 9 | import { easyUpdateView } from '#common/view/update.view.js' 10 | 11 | /** 12 | * 执行逻辑: 13 | * 目录检测 - 设置基础目录 14 | * 模式检测 - 检测是不是easy mode 15 | * [参数检测 - 执行对应参数] 16 | */ 17 | /** 18 | * 通用参数的执行逻辑 19 | * @param cmdOpts {{directory:string,language:string|boolean,easy:boolean,update:boolean,[key:string]:*}} 20 | * @param easyCallback {(baseDir:string)=>Promise} 21 | * @returns {Promise} 22 | */ 23 | export async function commonMode(cmdOpts, easyCallback) { 24 | // 启动一个额外的线程,并执行 worker.js 文件 25 | // const workerProcess = 26 | const jsPath = path.resolve(rootPath, currentEnv() === 'cli' ? 'origin/checkUpdate.js' : 'common/origin/checkUpdate.js') 27 | fork(jsPath) 28 | // todo 监听额外线程的消息 29 | // workerProcess.on('message', (message) => {}) 30 | // todo 监听额外线程的退出事件 31 | // workerProcess.on('exit', (code, signal) => {}) 32 | 33 | // 根据dir 参数来设置基本目录 34 | const baseDir = cmdOpts.directory ? path.join(process.cwd(), cmdOpts.directory) : process.cwd() 35 | /** 36 | * 语言设置 37 | * -带参设置语言 38 | * -无参获取语言 39 | */ 40 | if (cmdOpts.language) { 41 | if (cmdOpts.language !== true) { 42 | await easyLanguageView(cmdOpts.language) 43 | } 44 | else { 45 | const lang = await getQuestionLanguage() 46 | logger.info(`当前CLI语言环境为:${lang}`) 47 | } 48 | process.exit(0) 49 | } 50 | // 简单模式 51 | if (cmdOpts.easy) { 52 | await easyCallback(baseDir) 53 | process.exit(0) 54 | } 55 | if (cmdOpts.ver) { 56 | await checkVisionInfo() 57 | process.exit(0) 58 | } 59 | // 检测更新 60 | if (cmdOpts.update) { 61 | await easyUpdateView() 62 | process.exit(0) 63 | } 64 | return baseDir 65 | } 66 | -------------------------------------------------------------------------------- /common/utils/question-handler/getRandomId.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'node:fs' 2 | import { DefaultLimit } from '#common/constants/question.const.js' 3 | import { graphql } from '#common/utils/http/graphql.js' 4 | import { logger } from '#common/utils/logger/logger.js' 5 | import { getQuestionListJson } from '#resources/headers/questionListJson.js' 6 | 7 | /** 8 | * 获取指定页数的ids 9 | */ 10 | export async function getIds(index, limit = DefaultLimit) { 11 | const res = await graphql(getQuestionListJson(index, limit)) 12 | return res?.data?.problemsetQuestionList?.questions?.map(q => q.frontendQuestionId) 13 | } 14 | /** 15 | * 获取总数 16 | * @returns {Promise} 17 | */ 18 | export function getCount() { 19 | return graphql(getQuestionListJson(0)).then(res => res?.data?.problemsetQuestionList?.total) 20 | } 21 | /** 22 | * 获取随机的一个id 只要保证没有存在过就可以 23 | */ 24 | export async function getRandomId() { 25 | // 去除所有的标题 剩下的就是id 26 | const parse = name => name.replace(/\.[a-z0-9-]+$/i, '') 27 | // 获取一个 递归的获取 总会有一个 直到数量到达最大值 28 | const getOne = async (waitIndexList, localIds) => { 29 | const randomIndex = waitIndexList[random(waitIndexList.length)] 30 | const ids = await getIds(randomIndex) 31 | // 过滤后的结果 32 | const filtered = ids.filter(o => !localIds.includes(o)) 33 | if (randomIndex === undefined) 34 | return null 35 | if (filtered.length) { 36 | return filtered[random(filtered.length)] 37 | } 38 | else { 39 | waitIndexList.splice(waitIndexList.findIndex(i => i === randomIndex)) 40 | return await getOne(waitIndexList, localIds) 41 | } 42 | } 43 | // 所有本地题目的id 44 | const allLocalIds = readdirSync(process.cwd()).map(parse) 45 | // 最大的数量 46 | const maxLength = await getCount() 47 | const waitIndexList = Array.from({ 48 | length: Math.ceil(maxLength / DefaultLimit), 49 | }).map((_, i) => i) 50 | const one = await getOne(waitIndexList, allLocalIds) 51 | if (one === null) 52 | logger.info('恭喜!你已经刷完了所有的题目!') 53 | else return one 54 | } 55 | /** 56 | * 获取长度内的随机数组下标 57 | * @param len 默认值10 58 | * @returns {number} 59 | */ 60 | export function random(len = 10) { 61 | return Math.trunc((Math.random() * len) % len) 62 | } 63 | -------------------------------------------------------------------------------- /test/create.spec.js: -------------------------------------------------------------------------------- 1 | import { getQuestionById } from '#common/utils/question-getter/getQuestionById.js' 2 | import { getQuestionToday } from '#common/utils/question-getter/getQuestionToday.js' 3 | import { generateTemplateContent } from '#common/utils/question-handler/fulfillQuestion.js' 4 | import { afterEach, describe, expect, it, vi } from 'vitest' 5 | 6 | vi.mock('fs/promises', () => { 7 | return { 8 | writeFile: vi.fn(), 9 | } 10 | }) 11 | const funRegex = /var\s+(\w+)\s*=\s*function\s*\(([^)]*)\)\s*\{\s*([^}]*)\s*\}/ 12 | const isContainJsCode = input => funRegex.test(input) 13 | const isContainTestCase = input => input.includes('showLogs(') 14 | 15 | const mockKeys = ['id', 'slug', 'title', 'detail', 'lang', 'code', 'jsonExampleTestcases', 'exampleTestcases', 'date'] 16 | 17 | function isValidQuestion(res) { 18 | const content = generateTemplateContent(res) 19 | // 是否含有函数 20 | expect(isContainJsCode(content)).toBeTruthy() 21 | // 是否含有测试用例 22 | expect(isContainTestCase(content)).toBeTruthy() 23 | // 是否含有描述 24 | expect(content.includes('示例')).toBeTruthy() 25 | expect(content.includes('提示')).toBeTruthy() 26 | } 27 | 28 | describe('lc', () => { 29 | // 清楚mock历史记录 30 | afterEach(() => { 31 | vi.clearAllMocks() 32 | }) 33 | describe('with -t option', async () => { 34 | const res = await getQuestionToday() 35 | 36 | it('是否正确获取了今天的题目', () => { 37 | expect(Object.keys(res)).toEqual(mockKeys) 38 | }) 39 | it('是否正确的填充了今天的题目', async () => { 40 | isValidQuestion(res) 41 | }) 42 | }) 43 | describe('with -i option', async () => { 44 | const id_25 = '25' 45 | const res_25 = await getQuestionById(id_25) 46 | const id_LCS_03 = 'LCS 03' 47 | const res_LCS_03 = await getQuestionById(id_LCS_03) 48 | it('是否正确的获取了指定id的题目 25', async () => { 49 | expect(res_25.id).toEqual(id_25) 50 | }) 51 | it('是否正确填充了指定id的题目 25', async () => { 52 | isValidQuestion(res_25) 53 | }) 54 | it('是否正确的获取了指定id的题目 LCS 03', async () => { 55 | expect(res_LCS_03.id).toEqual(id_LCS_03) 56 | }) 57 | it('是否正确填充了指定id的题目 9', async () => { 58 | isValidQuestion(res_LCS_03) 59 | }) 60 | 61 | it('是否正确的获取了指定内容的题目 主题空间', async () => { 62 | const content = '主题空间' 63 | const res = await getQuestionById(content) 64 | expect(res?.id).toEqual(null) 65 | }) 66 | }) 67 | // describe('with -r option', async () => { 68 | 69 | // }) 70 | }) 71 | -------------------------------------------------------------------------------- /common/utils/store/controller/allQuestion.js: -------------------------------------------------------------------------------- 1 | import { exeOnce } from '#common/utils/store/store-realm.js' 2 | 3 | const oSign = '$object$' 4 | /** 5 | * 读取的时候:从对象的字符串转化到对象的对象 6 | * @param obj 7 | */ 8 | function parseQuestion(obj) { 9 | if (!obj) 10 | return null 11 | return Object.entries(obj).reduce((pre, [k, v]) => { 12 | pre[k] = typeof v == 'string' && v.startsWith(oSign) ? JSON.parse(v.replace(oSign, '')) : v 13 | return pre 14 | }, {}) 15 | } 16 | /** 17 | * 存入的时候:从对象的对象属性转化到字符串 18 | */ 19 | function stringifyQuestion(obj) { 20 | if (!obj) 21 | return null 22 | return ( 23 | Object.entries(obj)?.reduce((pre, [key, value]) => { 24 | pre[key] = value != null && typeof value === 'object' ? oSign + JSON.stringify(value) : value 25 | return pre 26 | }, {}) ?? {} 27 | ) 28 | } 29 | 30 | /** 31 | * 获取一个问题对象 32 | * @param id 33 | * @returns {Promise} 34 | */ 35 | export function getOneQuestion(id) { 36 | return exeOnce((realm) => { 37 | const question = realm.objectForPrimaryKey('AllQuestion', id) 38 | return parseQuestion(question?.toJSON()) 39 | }) 40 | } 41 | 42 | /** 43 | * 存一个问题对象 44 | * @param question 45 | * @returns {*} 46 | */ 47 | export function setOneQuestion(question) { 48 | return exeOnce((realm) => { 49 | let newQuestion 50 | realm.write(() => { 51 | newQuestion = realm.create('AllQuestion', stringifyQuestion(question), true) 52 | }) 53 | return newQuestion?.toJSON() 54 | }) 55 | } 56 | 57 | /** 58 | * 根据模式读取对象 59 | * @returns {unknown} 60 | */ 61 | export function getAllQuestion() { 62 | return exeOnce((realm) => { 63 | const all = realm.objects('AllQuestion') 64 | return all?.toJSON()?.map(parseQuestion) 65 | }) 66 | } 67 | 68 | /** 69 | * 存对象 70 | * @param questions 71 | * @returns {*} 72 | */ 73 | export function setAllQuestion(questions) { 74 | return exeOnce((realm) => { 75 | const newQuestions = [] 76 | realm.write(() => { 77 | for (const question of questions) { 78 | const data = stringifyQuestion(question) 79 | if (!data?.questionId) 80 | continue 81 | newQuestions.push(realm.create('AllQuestion', data, true)) 82 | } 83 | }) 84 | return newQuestions 85 | }) 86 | } 87 | 88 | /** 89 | * 删除全部 90 | */ 91 | export function deleteAllQuestion() { 92 | return exeOnce((realm) => { 93 | realm.write(() => { 94 | realm.delete(realm.objects('AllQuestion')) 95 | }) 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /common/utils/update/update.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { GITEE_URL, GITHUB_HOST, GITHUB_RAW, NPM_REGISTRY_URL, PackageName } from '#common/constants/question.const.js' 4 | import { rootPath } from '#common/utils/file/getRootPath.js' 5 | import { fetch_ } from '#common/utils/http/fetch_.js' 6 | import { url_join } from '#common/utils/http/urlJoin.js' 7 | 8 | // npm 中的 包地址 9 | const npmUrl = url_join(NPM_REGISTRY_URL, PackageName) 10 | const githubUrl = url_join(GITHUB_RAW, GITHUB_HOST, PackageName, 'master/package.json') 11 | const giteeUrl = url_join(GITEE_URL, GITHUB_HOST, PackageName, 'raw', 'master/package.json') 12 | 13 | /** 14 | * 获取远端npm库中的版本号 15 | */ 16 | export async function getNpmVersion() { 17 | try { 18 | const res = await fetch_(npmUrl, { method: 'GET' }) 19 | return res['dist-tags']?.latest || null 20 | } 21 | catch (e) { 22 | console.error('Error fetching npm version:', e) 23 | return null 24 | } 25 | } 26 | 27 | /** 28 | * 获取github的最新提交sha 29 | * @returns {Promise} 30 | */ 31 | export async function getGithubVersion() { 32 | try { 33 | const results = await Promise.allSettled([ 34 | fetch_(githubUrl, { method: 'GET' }), 35 | fetch_(giteeUrl, { method: 'GET' }), 36 | ]) 37 | 38 | for (const result of results) { 39 | if (result.status === 'fulfilled' && result.value?.version) { 40 | return result.value.version 41 | } 42 | } 43 | return null 44 | } 45 | catch (e) { 46 | console.error('Error fetching GitHub version:', e) 47 | return null 48 | } 49 | } 50 | 51 | export function getLocalVersion() { 52 | try { 53 | const packageJsonPath = path.resolve(rootPath, 'package.json') 54 | const { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) 55 | return version 56 | } 57 | catch (e) { 58 | console.error('Error reading local version:', e) 59 | return null 60 | } 61 | } 62 | 63 | /** 64 | * 检测整体的更新状况 65 | * @returns {Promise<{localVersion: string|null, npmVersion: string|null, githubVersion: string|null, isCliUpdate: boolean, isGithubUpdate: boolean}>} 66 | */ 67 | export async function checkUpdate() { 68 | const [remote, github, local] = await Promise.all([ 69 | getNpmVersion(), 70 | getGithubVersion(), 71 | Promise.resolve(getLocalVersion()), 72 | ]) 73 | 74 | return { 75 | localVersion: local, 76 | npmVersion: remote, 77 | githubVersion: github, 78 | isCliUpdate: remote !== null && remote !== local, 79 | isGithubUpdate: github !== null && github !== local, 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /resources/text/examples.js: -------------------------------------------------------------------------------- 1 | export const lcExamples = ` 2 | Examples: 3 | # Command with no parameters 4 | $ lc // Get a question for today. 5 | $ lc 1314 // Get a question by its identity. 6 | # Exclusive commands 7 | $ lc -t // Full text command to get a question for today. 8 | $ lc -i 1314 // Full text command to get a question by its identity. 9 | $ lc -r // Get a question randomly. 10 | # Other instructions 11 | $ lc -e // Easy mode to create a question. 12 | $ lc -d src // Use the relative path to the source folder. 13 | $ lc -u // Check the version to determine whether to update to the latest one. 14 | $ lc -l // Get the code language of question. 15 | $ lc -l java // Set the code language of question. 16 | $ lc -a // Fetch all questions from server and store them locally, prepare database for lf command. 17 | $ lc -v // Check the version info and some extra info about leetcode-practice. 18 | $ lc -h // Check the help information. 19 | 20 | ` 21 | 22 | export const lkExamples = ` 23 | Examples: 24 | # Command with no parameters 25 | $ lk // Check today's question. 26 | $ lk 1314 // Check the question by its ID. 27 | # Exclusive commands 28 | $ lk -t // Full text command to check today's question. 29 | $ lk -i 1314 // Full text command to check the question by its ID. 30 | $ lk -r // Check a randomly generated question. 31 | # Other instructions 32 | $ lk -e // Easy mode to check a question. 33 | $ lk -d src // Use the relative path to the source folder. 34 | $ lk -l // Get the code language of question. 35 | $ lk -l java // Set the code language of question. 36 | $ lk -u // Check the version to determine whether to update to the latest one. 37 | $ lk -v // Check the version info and some extra info about leetcode-practice. 38 | $ lk -h // Display the help information. 39 | 40 | ` 41 | 42 | export const lfExamples = ` 43 | Examples: 44 | # Command with no parameters 45 | nothing... 46 | # Exclusive commands 47 | nothing... 48 | # Other instructions 49 | $ lf -e // Easy mode to check a question. 50 | $ lf -d src // Use the relative path to the source folder. 51 | $ lf -l // Get the code language of question. 52 | $ lf -l java // Set the code language of question. 53 | $ lf -u // Check the version to determine whether to update to the latest one. 54 | $ lf -v // Check the version info and some extra info about leetcode-practice. 55 | $ lf -h // Display the help information. 56 | 57 | ` 58 | -------------------------------------------------------------------------------- /common/utils/store/controller/store.js: -------------------------------------------------------------------------------- 1 | import { typeof_ } from '#common/utils/etc/typeof_.js' 2 | import { exeOnce } from '#common/utils/store/store-realm.js' 3 | 4 | /** 5 | * 转化数据到特殊格式字符串 6 | * @param data 7 | * @returns {string} 8 | */ 9 | function convData(data) { 10 | const type = typeof_(data) 11 | const dataStr = type === 'object' ? JSON.stringify(data) : data.toString() 12 | const prefix = `$<${type}>$` 13 | return prefix + dataStr 14 | } 15 | /** 16 | * 转化字符串到数据 17 | * @param dataStr 18 | * @returns {any} 19 | */ 20 | function parseData(dataStr) { 21 | const prefixReg = /^\$<.+>\$/m 22 | if (!prefixReg.test(dataStr)) 23 | return null 24 | 25 | const type = dataStr.match(prefixReg)[0].replace(/[$<>]+/g, '') 26 | const remainStr = dataStr.replace(prefixReg, '') 27 | switch (type) { 28 | case 'object': 29 | return JSON.parse(remainStr) 30 | case 'bigint': 31 | return BigInt(remainStr) 32 | case 'boolean': 33 | return remainStr === 'true' 34 | case 'number': 35 | return Number(remainStr) 36 | case 'function': 37 | return () => remainStr 38 | case 'string': 39 | default: 40 | return remainStr 41 | } 42 | } 43 | 44 | /** 45 | * 设置一项记录 46 | * @param key 47 | * @param value 48 | * @returns {Promise} 49 | */ 50 | export function setStore(key, value) { 51 | return exeOnce((realm) => { 52 | let newStore 53 | realm.write(() => { 54 | const oldStore = realm.objects('Store').filtered(`key = "${key}"`)?.[0] 55 | oldStore && realm.delete(oldStore) 56 | newStore = realm.create('Store', { key, value: convData(value) }) 57 | }) 58 | return newStore.toJSON() 59 | }) 60 | } 61 | 62 | /** 63 | * 获取记录值 64 | * @param key 65 | * @returns {Promise} 66 | */ 67 | export function getStore(key) { 68 | return exeOnce((realm) => { 69 | const all = realm.objects('Store') 70 | const storeObj = all.filtered('key=$0', key)?.[0]?.toJSON() 71 | return parseData(storeObj?.value) 72 | }) 73 | } 74 | 75 | /** 76 | * 删除某一项记录 77 | * @param key 78 | * @returns {Promise} 79 | */ 80 | export function deleteStore(key) { 81 | return exeOnce((realm) => { 82 | realm.write(() => { 83 | realm.delete(realm.objects('Store').filtered('key=$0', key)) 84 | }) 85 | }) 86 | } 87 | 88 | /** 89 | * 清理全部缓存 90 | * @param mode 91 | * @returns {Promise} 92 | */ 93 | export function clearStore() { 94 | return exeOnce((realm) => { 95 | realm.write(() => { 96 | realm.delete(realm.objects('Store')) 97 | }) 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /common/utils/question-handler/showLogs.js: -------------------------------------------------------------------------------- 1 | import v8 from 'node:v8' 2 | import { Table } from 'console-table-printer' 3 | import { isSameData } from '../functions/isSameData.js' 4 | import { getFileSize } from '../functions/sizeUtil.js' 5 | import { setDataStructure } from './parseStructure.js' 6 | 7 | /** 8 | * 执行并输出时间和内存 9 | * @param fnName 10 | * @param param 传入数组 全部通过解构来做 11 | * @param compare 12 | * @param compareStruct 13 | * @returns {{预期结果: string, 执行结果: string, 内存占用: string, 测试结果: (string), 执行用时: string}} 14 | */ 15 | export function parseLog(fnName, param, compare, compareStruct) { 16 | // 记录开始时间 17 | const startTime = performance.now() 18 | // 获取函数执行前的内存使用情况 19 | const startHeapStatsArray = v8.getHeapSpaceStatistics() 20 | const callVal = fnName(...param) 21 | const [parsedCompareArr] = setDataStructure([callVal], compareStruct, 'return') 22 | // 获取函数执行后的内存使用情况 23 | const endHeapStatsArray = v8.getHeapSpaceStatistics() 24 | // 记录结束时间 25 | const endTime = performance.now() 26 | const startHeapStats = startHeapStatsArray.reduce((prev, curr) => (prev += curr.space_used_size), 0) 27 | const endHeapStats = endHeapStatsArray.reduce((prev, curr) => (prev += curr.space_used_size), 0) 28 | 29 | return { 30 | 测试结果: isSameData(parsedCompareArr, compare) ? '通过' : '未通过', 31 | 预期结果: JSON.stringify(compare), 32 | 执行结果: JSON.stringify(parsedCompareArr), 33 | 执行用时: `${Number(endTime - startTime).toFixed(4)}ms`, 34 | 内存占用: getFileSize(endHeapStats - startHeapStats), 35 | } 36 | } 37 | 38 | export function showLogs(fnName, paramMap, compareMap) { 39 | const logsItems = [] 40 | const { data: paramArr, structure: paramStruct } = paramMap 41 | const { data: compareArr, structure: compareStruct } = compareMap 42 | 43 | paramArr.forEach((param, index) => { 44 | const parsedParma = setDataStructure(param, paramStruct) 45 | const logItem = parseLog(fnName, parsedParma, compareArr[index], compareStruct) 46 | logsItems.push(logItem) 47 | }) 48 | 49 | const logTable = new Table({ 50 | columns: [ 51 | { name: '测试结果', title: '测试结果', alignment: 'center', maxLen: 10 }, 52 | { name: '预期结果', title: '预期结果', alignment: 'center', maxLen: 40 }, 53 | { name: '执行结果', title: '执行结果', alignment: 'center', maxLen: 40 }, 54 | { name: '执行用时', title: '执行用时', alignment: 'center', maxLen: 10 }, 55 | { name: '内存占用', title: '内存占用', alignment: 'center', maxLen: 10 }, 56 | ], 57 | }) 58 | logsItems.forEach((item) => { 59 | for (const key in item) { 60 | if (key === '预期结果' || key === '执行结果') 61 | item[key] = item[key]?.length >= 40 ? `${item[key].slice(0, 37)}...` : item[key] 62 | } 63 | 64 | logTable.addRow(item, { 65 | color: item['测试结果'] === '通过' ? 'green' : 'red', 66 | }) 67 | }) 68 | logTable.printTable() 69 | } 70 | -------------------------------------------------------------------------------- /common/view/create.view.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { logger } from '#common/utils/logger/logger.js' 3 | import { getQuestionById } from '#common/utils/question-getter/getQuestionById.js' 4 | import { getQuestionRandom } from '#common/utils/question-getter/getQuestionRandom.js' 5 | import { getQuestionToday } from '#common/utils/question-getter/getQuestionToday.js' 6 | import { createQuestion } from '#common/utils/question-handler/createQuestion.js' 7 | import { createQuestionCopy } from '#common/utils/question-handler/createQuestionCopy.js' 8 | import { getQuestionFileName } from '#common/utils/question-handler/getQuestionFileName.js' 9 | import { setQuestion } from '#common/utils/store/controller/question.js' 10 | import inquirer from 'inquirer' 11 | 12 | export async function easyCreateView(baseDir = process.cwd()) { 13 | const modeQuestion = [ 14 | { 15 | type: 'list', 16 | name: 'mode', 17 | message: '请选择创建问题的模式:', 18 | choices: ['today', 'identity', 'random'], 19 | }, 20 | ] 21 | // 第一个问题 选择的模式 22 | const { mode } = await inquirer.prompt(modeQuestion, null) 23 | const identityQuestion = [ 24 | { 25 | type: 'input', 26 | name: 'identity', 27 | message: '请输入题目编号:', 28 | }, 29 | ] 30 | let question 31 | switch (mode) { 32 | case 'identity': { 33 | const { identity } = await inquirer.prompt(identityQuestion, null) 34 | logger.info(identity) 35 | question = await getQuestionById(identity) 36 | break 37 | } 38 | case 'random': 39 | question = await getQuestionRandom() 40 | break 41 | case 'today': 42 | default: 43 | question = await getQuestionToday() 44 | break 45 | } 46 | const store = await setQuestion(mode, question) 47 | if (!store) 48 | console.warn(`[create][${mode}]问题[${question.title}]未成功缓存`) 49 | // 创建题目 50 | const questionFileName = getQuestionFileName(question) 51 | let questionDir = path.join(baseDir, questionFileName) 52 | // 创建路径确认 53 | const pathRightQuestion = [ 54 | { 55 | type: 'confirm', 56 | name: 'dirRight', 57 | message: `是否在目录[ ${baseDir} ]下创建题目[ ${questionFileName} ]?`, 58 | }, 59 | ] 60 | const { dirRight } = await inquirer.prompt(pathRightQuestion, null) 61 | if (!dirRight) { 62 | const newDirQuestion = [ 63 | { 64 | type: 'input', 65 | name: 'newDir', 66 | message: `请选择新目录(基础地址为${baseDir})[按回车[Enter]终止操作]:`, 67 | }, 68 | ] 69 | const { newDir } = await inquirer.prompt(newDirQuestion, null) 70 | if (!newDir) { 71 | logger.info('[LC-logger]用户终止操作~') 72 | process.exit(0) 73 | } 74 | questionDir = path.join(path.join(baseDir, newDir), `${questionFileName}`) 75 | } 76 | let filePath = await createQuestion(question, questionDir) 77 | if (!filePath) 78 | filePath = await createQuestionCopy(question, questionDir) 79 | 80 | logger.info(`题目[${questionFileName}]创建完成!\n文件地址为: ${filePath}`) 81 | process.exit(0) 82 | } 83 | -------------------------------------------------------------------------------- /common/utils/question-handler/getTestCase.js: -------------------------------------------------------------------------------- 1 | import { DefaultLang } from '#common/constants/question.const.js' 2 | import { setBlockComment } from '#common/utils/question-handler/questionLanguage.js' 3 | import { removeDomTags } from '../functions/removeDomTags.js' 4 | import { getDataStructure } from './parseStructure.js' 5 | 6 | /** 7 | * 输出的日志 8 | * @param question 9 | * @param functionName 10 | * @param cases 11 | * @param expires 12 | * @returns {`showLogs( 13 | ${string}, 14 | { 15 | data: [${string}], 16 | structure: ${string} 17 | }, 18 | { 19 | data: [${string}], 20 | structure: ${string} 21 | } 22 | )`} 23 | */ 24 | function logsTemplate(question, functionName, cases, expires) { 25 | return `showLogs( 26 | ${functionName}, 27 | { 28 | data: [${cases}], 29 | structure: ${JSON.stringify(getDataStructure(question.code))} 30 | }, 31 | { 32 | data: [${expires}], 33 | structure: ${JSON.stringify(getDataStructure(question.code, 'return'))} 34 | } 35 | )` 36 | } 37 | 38 | /** 39 | * test case 需要从两个地方拿到内容 40 | * 1.详情:拿到默认的几个用例 41 | * 2.函数名:拿到函数名创建用例函数 42 | * @param question 43 | * @returns {string} 44 | */ 45 | export function getTestCase(question) { 46 | // 完整的一条语句的reg 47 | const inputReg = /(<[a-z]+>)?输入[:|:](<\/[a-z]+>)?.+\n/gi 48 | const inputStartReg = /(<[a-z]+>)?输入[:|:]/gi 49 | // 输出的reg 50 | const outputReg = /(<[a-z]+>)?输出[:|:](<\/[a-z]+>)?.+\n/gi 51 | const outputStartReg = /(<[a-z]+>)?输出[:|:]/gi 52 | // 结尾 53 | const endReg = /(<\/[a-z]+>)?/gi 54 | 55 | const detail = question.detail?.replaceAll('`', '') 56 | const cases = detail?.match(inputReg)?.map( 57 | str => 58 | `[${removeDomTags( 59 | str 60 | ?.replace(inputStartReg, '') 61 | ?.replace(endReg, '') 62 | ?.replace('\n', '') 63 | .replace(/[a-z]+ =/gi, ''), 64 | )}]`, 65 | ) 66 | const expires = detail?.match(outputReg)?.map(str => 67 | removeDomTags( 68 | str 69 | ?.replace(outputStartReg, '') 70 | ?.replace(endReg, '') 71 | ?.replace('\n', '') 72 | .replace(/[a-z]+ =/gi, ''), 73 | ), 74 | ) 75 | if (question.lang === DefaultLang) { 76 | const functionName = question.code 77 | ?.match(/(var|let|const).+=/g)?.[0] 78 | ?.replace(/((var|let|const)|=)\s?/g, '') 79 | .trim() 80 | if (!functionName) 81 | return '' 82 | return setBlockComment(question.lang, 'Test Cases') + logsTemplate(question, functionName, cases, expires) 83 | } 84 | else { 85 | // 其他语言无法支持测试 只能提供测试数据 86 | // 生成注释语句 87 | let showText = `暂无法支持除JS外的语言测试,提取的一些入参和返回值供自行测试,每一个case中的第一行为入参,第二行为返回值\n` 88 | for (let i = 0; i < Math.max(cases?.length ?? 0, expires?.length ?? 0); i++) { 89 | showText += `case ${i + 1}:\n` 90 | showText += `${cases?.[i]}\n` ?? '[参数获取错误]\n' 91 | showText += `${expires?.[i]}\n` ?? '[返回值获取错误]\n' 92 | } 93 | showText += `\n` 94 | return setBlockComment(question.lang, showText) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode-practice", 3 | "type": "module", 4 | "version": "1.0.9", 5 | "description": "A powerful practice platform for leetcode.Using any way you want to create questions.", 6 | "author": { 7 | "name": "EternalHeart", 8 | "email": "hao131462@qq.com" 9 | }, 10 | "publishConfig": { 11 | "registry": "https://registry.npmjs.org/" 12 | }, 13 | "license": "ISC", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/wh131462/leetcode-practice.git" 17 | }, 18 | "imports": { 19 | "#common/*": "./common/*", 20 | "#resources/*": "./resources/*" 21 | }, 22 | "bin": { 23 | "lk": "bin/lk.js", 24 | "lf": "bin/lf.js", 25 | "lc": "bin/lc.js" 26 | }, 27 | "scripts": { 28 | "lc": "node bin/lc.js -d src", 29 | "lk": "node bin/lk.js -d src", 30 | "lf": "node bin/lf.js -d src", 31 | "build-cli": "node esbuild.config.js", 32 | "publish-cli": "cd pl-cli && npm publish --registry https://registry.npmjs.org/", 33 | "unpublish-cli": "cd pl-cli && npm unpublish --force --registry https://registry.npmjs.org/", 34 | "commit": "cz", 35 | "test": "vitest run --reporter=verbose --teardown-timeout=5000 --pool=forks", 36 | "coverage": "vitest run --coverage --pool=forks", 37 | "format": "prettier --write .", 38 | "lint": "eslint . --fix", 39 | "lint:all": "eslint .", 40 | "prepare": "husky install", 41 | "create-color-font": "node scripts/create-color-font.js", 42 | "release": "release-it", 43 | "try-release": "release-it --dry-run", 44 | "rebase": "HUSKY=0 git rebase " 45 | }, 46 | "dependencies": { 47 | "@inquirer/input": "^2.0.1", 48 | "@inquirer/select": "^4.0.0", 49 | "chalk": "^5.3.0", 50 | "commander": "^12.0.0", 51 | "console-table-printer": "^2.12.0", 52 | "glob": "^10.3.10", 53 | "inquirer": "^12.0.0", 54 | "ora": "^8.0.1", 55 | "realm": "^12.6.2" 56 | }, 57 | "devDependencies": { 58 | "@antfu/eslint-config": "^3.8.0", 59 | "@commitlint/cli": "^19.1.0", 60 | "@commitlint/config-conventional": "^19.1.0", 61 | "@release-it/bumper": "^6.0.1", 62 | "@release-it/conventional-changelog": "^8.0.1", 63 | "@types/node": "^22.7.7", 64 | "@vitest/coverage-v8": "^1.2.2", 65 | "commitizen": "^4.2.5", 66 | "cz-conventional-changelog": "^3.3.0", 67 | "esbuild": "^0.24.0", 68 | "eslint": "^9.0.0", 69 | "eslint-config-airbnb-base": "^15.0.0", 70 | "eslint-config-prettier": "^9.1.0", 71 | "eslint-plugin-import": "^2.29.1", 72 | "eslint-plugin-prettier": "^5.1.3", 73 | "gradient-string": "^3.0.0", 74 | "husky": "^9.0.11", 75 | "lint-staged": "^15.2.2", 76 | "prettier": "^3.2.5", 77 | "release-it": "^17.1.1", 78 | "rimraf": "^5.0.5", 79 | "typescript": "^5.4.2", 80 | "vite": "^5.0.11", 81 | "vitest": "^1.2.2" 82 | }, 83 | "lint-staged": { 84 | "*": [ 85 | "eslint", 86 | "prettier --write" 87 | ] 88 | }, 89 | "config": { 90 | "commitizen": { 91 | "path": "./node_modules/cz-conventional-changelog" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /esbuild.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { rootPath } from '#common/utils/file/getRootPath.js' 4 | import { logger } from '#common/utils/logger/logger.js' 5 | import esbuild from 'esbuild' 6 | 7 | // 读取 package.json 文件内容 8 | const packageJson = JSON.parse(fs.readFileSync(path.resolve(rootPath, 'package.json'), 'utf-8')) 9 | const esbuildConfig = { 10 | entryPoints: ['bin/lk.js', 'bin/lf.js', 'bin/lc.js'], 11 | outdir: 'pl-cli/bin', 12 | platform: 'node', 13 | target: ['node20'], 14 | format: 'esm', 15 | bundle: true, 16 | minify: true, 17 | packages: 'external', 18 | define: { 19 | 'process.env.VERSION': JSON.stringify(packageJson.version), 20 | }, 21 | } 22 | const buildBinConfig = { 23 | lk: 'bin/lk.js', 24 | lf: 'bin/lf.js', 25 | lc: 'bin/lc.js', 26 | } 27 | const publishExcludeFields = ['scripts', 'devDependencies', 'imports', 'main', 'config', 'packageManager'] 28 | // 清理文件 29 | function clean() { 30 | return new Promise((resolve) => { 31 | fs.rm(path.resolve(rootPath, 'pl-cli'), { recursive: true }, (err) => { 32 | if (err) 33 | resolve() 34 | else resolve() 35 | }) 36 | }) 37 | } 38 | 39 | /** 40 | * 复制文档 41 | */ 42 | function copyDocs() { 43 | // 创建docs 44 | const docs = ['README.md', 'README_CN.md', 'README_JP.md'] 45 | docs.forEach((doc) => { 46 | fs.copyFileSync(path.resolve(rootPath, doc), path.resolve(rootPath, `pl-cli/${doc}`)) 47 | }) 48 | fs.copyFileSync(path.resolve(rootPath, 'LICENSE'), path.resolve(rootPath, 'pl-cli/LICENSE')) 49 | } 50 | 51 | /** 52 | * 重写包文件 53 | */ 54 | function rewritePackageFile() { 55 | const newPackageJson = Object.assign(packageJson, { 56 | bin: buildBinConfig, 57 | }) 58 | publishExcludeFields?.forEach((key) => { 59 | delete newPackageJson[key] 60 | }) 61 | fs.writeFileSync(path.resolve(rootPath, 'pl-cli/package.json'), JSON.stringify(newPackageJson)) 62 | } 63 | 64 | /** 65 | * 创建原始目录下的文件 需要将js转化成压缩后的形式 66 | */ 67 | function createOrigin() { 68 | const originFiles = fs 69 | .readdirSync(path.resolve(rootPath, 'common/origin')) 70 | ?.filter(path => path.endsWith('.js')) 71 | .map(file => path.resolve(rootPath, `common/origin/${file}`)) 72 | esbuild.buildSync({ 73 | entryPoints: originFiles, 74 | minify: true, 75 | bundle: true, 76 | outdir: 'pl-cli/origin', 77 | platform: 'node', 78 | target: ['node20'], 79 | packages: 'external', 80 | format: 'esm', 81 | }) 82 | } 83 | /** 84 | * 构建完成之后的流程 85 | */ 86 | function afterBuild() { 87 | copyDocs() 88 | rewritePackageFile() 89 | createOrigin() 90 | } 91 | 92 | /** 93 | * 主进程 94 | */ 95 | async function main() { 96 | await clean() 97 | await esbuild 98 | .build(esbuildConfig) 99 | .then(() => { 100 | // 构建完成后执行的操作 101 | afterBuild() 102 | logger.info('[LP]脚本打包完成,请查看目录[ pl-cli ].') 103 | process.exit(0) 104 | }) 105 | .catch((e) => { 106 | logger.error('[LP]脚本打包失败', e) 107 | process.exit(1) 108 | }) 109 | } 110 | 111 | await main() 112 | -------------------------------------------------------------------------------- /bin/lc.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import { DefaultVer } from '#common/constants/question.const.js'; 3 | import { commonMode } from '#common/utils/cli-utils/commonMode.js'; 4 | import { create } from '#common/utils/cli-utils/create.js'; 5 | import { createQuestionById } from '#common/utils/cli-utils/createQuestion.js'; 6 | import { getArgs } from '#common/utils/cli-utils/getArgs.js'; 7 | import { referMode } from '#common/utils/cli-utils/referMode.js'; 8 | import { logger } from '#common/utils/logger/logger.js'; 9 | import { getAllQuestionList } from '#common/utils/question-getter/getAllQuestionList.js'; 10 | import { getQuestionRandom } from '#common/utils/question-getter/getQuestionRandom.js'; 11 | import { getQuestionToday } from '#common/utils/question-getter/getQuestionToday.js'; 12 | import { setAllQuestion } from '#common/utils/store/controller/allQuestion.js'; 13 | import { easyCreateView } from '#common/view/create.view.js'; 14 | import { aim } from '#resources/text/aim.js'; 15 | import { artFontLogo } from '#resources/text/art-font-logo.js'; 16 | import { description } from '#resources/text/description.js'; 17 | import { lcExamples } from '#resources/text/examples.js'; 18 | import { love } from '#resources/text/love.js'; 19 | import { program } from 'commander'; 20 | 21 | const version = process.env.VERSION ?? DefaultVer 22 | program 23 | .version(version) 24 | .description(`${description}\n${artFontLogo}\n${aim}`) 25 | .addHelpText('after', lcExamples + love) 26 | .arguments('[identity]') 27 | .option('-t, --today', 'Get a question today.') 28 | .option('-i, --identity ', 'Specify a question by identity.') 29 | .option('-r, --random', 'Get a question randomly.') 30 | .option('-e, --easy', 'Use easy mode.') 31 | .option('-d, --directory ', 'Set the question directory.') 32 | .option('-l, --language [language]', 'Set/Get the code language of question.') 33 | .option('-a, --all', 'Get all questions.') 34 | .option('-v, --ver', 'Check the version info and some extra info about leetcode-practice.') 35 | .option('-u, --update', 'Check the version to determine whether to update to the latest one.') 36 | .parse(process.argv) 37 | 38 | const cmdArgs = program.args 39 | const cmdOpts = program.opts() 40 | // 通用参数执行 41 | const baseDir = await commonMode(cmdOpts, easyCreateView) 42 | // 模式对应的action 43 | export const callModeAction = { 44 | today: () => { 45 | getQuestionToday().then((question) => { 46 | create('today', question, baseDir).then(() => { 47 | process.exit(0) 48 | }); 49 | }) 50 | }, 51 | random: () => { 52 | getQuestionRandom().then((question) => { 53 | create('random', question, baseDir).then(() => { 54 | process.exit(0) 55 | }); 56 | }) 57 | }, 58 | identity: async (id) => { 59 | await createQuestionById(id, baseDir) 60 | process.exit(0) 61 | }, 62 | all: async () => { 63 | const allQuestionData = await getAllQuestionList() 64 | await setAllQuestion(allQuestionData) 65 | logger.info('拉取全部题目成功!') 66 | process.exit(0) 67 | } 68 | } 69 | // 获取模式和参数 70 | const mode = referMode(cmdArgs, cmdOpts) 71 | const args = getArgs(mode, cmdArgs, cmdOpts) 72 | // 执行指令分发 73 | await callModeAction[mode](args) 74 | -------------------------------------------------------------------------------- /test/paseDataStructure.spec.js: -------------------------------------------------------------------------------- 1 | import { getDataStructure } from '#common/utils/question-handler/parseStructure.js' 2 | import { expect, it } from 'vitest' 3 | 4 | const mockJSDOC_multiple = `/** 5 | * Definition for singly-linked list. 6 | * function ListNode(val, next) { 7 | * this.val = (val===undefined ? 0 : val) 8 | * this.next = (next===undefined ? null : next) 9 | * } 10 | */ 11 | /** 12 | * @param {ListNode} list1 13 | * @param {number} a 14 | * @param {number} b 15 | * @param {ListNode} list2 16 | * @return {ListNode} 17 | */ 18 | var mergeInBetween = function(list1, a, b, list2) { 19 | console.log(list1.val,list1) 20 | };` 21 | 22 | const mockJSDOC_single = `/** 23 | * Definition for singly-linked list. 24 | * function ListNode(val, next) { 25 | * this.val = (val===undefined ? 0 : val) 26 | * this.next = (next===undefined ? null : next) 27 | * } 28 | */ 29 | /** 30 | * @param {ListNode} head 31 | * @return {ListNode} 32 | */ 33 | var sortList = function(head) { 34 | 35 | };` 36 | const return_void = `/** 37 | * Definition for singly-linked list. 38 | * function ListNode(val, next) { 39 | * this.val = (val===undefined ? 0 : val) 40 | * this.next = (next===undefined ? null : next) 41 | * } 42 | */ 43 | /** 44 | * @param {ListNode} head 45 | * @return {void} Do not return anything, modify head in-place instead. 46 | */ 47 | var reorderList = function(head) { 48 | 49 | };` 50 | 51 | const mockJSDOC_ListNodeArray = `/** 52 | * Definition for singly-linked list. 53 | * function ListNode(val, next) { 54 | * this.val = (val===undefined ? 0 : val) 55 | * this.next = (next===undefined ? null : next) 56 | * } 57 | */ 58 | /** 59 | * @param {ListNode[]} lists 60 | * @return {ListNode} 61 | */ 62 | var mergeKLists = function(lists) { 63 | // console.log(lists.length, lists.val) 64 | // const lists_1 = new ListNode(null) 65 | for (let i = 0; i < lists.length; i++) { 66 | let node = lists[i]; 67 | if(node) { 68 | console.log(node.val) 69 | 70 | } 71 | // while (node) { 72 | // list.push(node.val); 73 | // node = node.next; 74 | // } 75 | } 76 | 77 | 78 | };` 79 | 80 | const array = ['ListNode', 'number', 'number', 'ListNode'] 81 | 82 | it('获取入参的数据结构 多参 是数组', () => { 83 | expect(getDataStructure(mockJSDOC_multiple)).toBeInstanceOf(Array) 84 | }) 85 | it('获取入参的数据结构 多参 匹配值', () => { 86 | expect(getDataStructure(mockJSDOC_multiple)).toEqual(array) 87 | }) 88 | it('获取入参的数据结构 单参 是数组', () => { 89 | expect(getDataStructure(mockJSDOC_single)).toBeInstanceOf(Array) 90 | }) 91 | it('获取入参的数据结构 单参 匹配值', () => { 92 | expect(getDataStructure(mockJSDOC_single)).toEqual(['ListNode']) 93 | }) 94 | 95 | it('获取入参的数据结构 单参 ListNode[]', () => { 96 | expect(getDataStructure(mockJSDOC_ListNodeArray, 'param')).toEqual(['ListNode[]']) 97 | }) 98 | it('获取返回值的数据结构 单参 匹配值', () => { 99 | expect(getDataStructure(mockJSDOC_single, 'return')).toEqual(['ListNode']) 100 | }) 101 | 102 | it('获取返回值的数据结构 多参 匹配值', () => { 103 | expect(getDataStructure(mockJSDOC_multiple, 'return')).toEqual(['ListNode']) 104 | }) 105 | it('获取返回值的数据结构 单参 void', () => { 106 | expect(getDataStructure(return_void, 'return')).toEqual(['void']) 107 | }) 108 | -------------------------------------------------------------------------------- /common/view/check.view.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { getCountBySameName } from '#common/utils/file/getCountBySameName.js' 3 | import { getFileListBySameName } from '#common/utils/file/getFileListBySameName.js' 4 | import { logger } from '#common/utils/logger/logger.js' 5 | import { getQuestionById } from '#common/utils/question-getter/getQuestionById.js' 6 | import { checkQuestionByPath } from '#common/utils/question-handler/checkQuestionByPath.js' 7 | import { getQuestionFileName } from '#common/utils/question-handler/getQuestionFileName.js' 8 | import { getQuestionByMode } from '#common/utils/store/controller/question.js' 9 | import inquirer from 'inquirer' 10 | 11 | export async function easyCheckView() { 12 | const modeQuestion = [ 13 | { 14 | type: 'list', 15 | name: 'mode', 16 | message: '请选择检查问题的模式:', 17 | choices: ['today', 'identity', 'random'], 18 | }, 19 | ] 20 | // 第一个问题 选择的模式 21 | const { mode } = await inquirer.prompt(modeQuestion, null) 22 | const identityQuestion = [ 23 | { 24 | type: 'input', 25 | name: 'identity', 26 | message: '请输入题目编号:', 27 | }, 28 | ] 29 | let question 30 | switch (mode) { 31 | case 'identity': { 32 | const { identity } = await inquirer.prompt(identityQuestion, null) 33 | question = !identity ? await getQuestionByMode(mode) : await getQuestionById(identity) 34 | break 35 | } 36 | case 'random': 37 | question = await getQuestionByMode(mode) 38 | break 39 | case 'today': 40 | default: 41 | question = await getQuestionByMode(mode) 42 | break 43 | } 44 | // 检查题目 45 | const questionFileName = getQuestionFileName(question) 46 | const currentDir = process.cwd() 47 | let questionDir = path.join(currentDir, questionFileName) 48 | // 创建路径确认 49 | const pathRightQuestion = [ 50 | { 51 | type: 'confirm', 52 | name: 'dirRight', 53 | message: `是否检测当前目录[ ${currentDir} ]下的题目[ ${questionFileName} ]?`, 54 | }, 55 | ] 56 | const { dirRight } = await inquirer.prompt(pathRightQuestion, null) 57 | if (!dirRight) { 58 | const newDirQuestion = [ 59 | { 60 | type: 'input', 61 | name: 'newDir', 62 | message: `请选择新目录(基础地址为${currentDir})[按回车[Enter]终止操作]:`, 63 | }, 64 | ] 65 | const { newDir } = await inquirer.prompt(newDirQuestion, null) 66 | if (!newDir) { 67 | logger.info('[LK-logger]用户终止操作~') 68 | process.exit(0) 69 | } 70 | questionDir = path.join(path.join(process.cwd(), newDir), `${questionFileName}`) 71 | } 72 | const questionParentDir = path.dirname(questionDir) 73 | // 先检测有几个副本 74 | if (getCountBySameName(questionParentDir, questionFileName) > 1) { 75 | const selectQuestionQuestion = [ 76 | { 77 | type: 'list', 78 | name: 'selectQuestion', 79 | message: `题目[ ${questionFileName} ]有多个副本,请选择要检测的副本:`, 80 | choices: getFileListBySameName(questionParentDir, questionFileName), 81 | }, 82 | ] 83 | // 选择其中一个副本进行检查 84 | const { selectQuestion } = await inquirer.prompt(selectQuestionQuestion, null) 85 | questionDir = path.join(questionParentDir, selectQuestion) 86 | logger.info(`用户选择题目[ ${questionFileName}]的副本[ ${selectQuestion}]进行检测`) 87 | } 88 | const filePath = path.join(questionDir, `question${question.lang}`) 89 | await checkQuestionByPath(filePath) 90 | logger.info(`题目[${questionFileName}]检查完成!\n文件地址为: ${filePath}`) 91 | process.exit(0) 92 | } 93 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | hao131462@qq.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /bin/lk.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import fs from 'node:fs' 3 | import path from 'node:path' 4 | import { DefaultLang, DefaultVer } from '#common/constants/question.const.js' 5 | import { commonMode } from '#common/utils/cli-utils/commonMode.js' 6 | import { getArgs } from '#common/utils/cli-utils/getArgs.js' 7 | import { referMode } from '#common/utils/cli-utils/referMode.js' 8 | import { getFilePathById } from '#common/utils/file/getFilePathById.js' 9 | import { getQuestionFileInDir } from '#common/utils/file/getQuestionInDir.js' 10 | import { logger } from '#common/utils/logger/logger.js' 11 | import { checkQuestionByPath } from '#common/utils/question-handler/checkQuestionByPath.js' 12 | import { getQuestionChineseName } from '#common/utils/question-handler/getQuestionChineseName.js' 13 | import { getQuestionFileName } from '#common/utils/question-handler/getQuestionFileName.js' 14 | import { getQuestionFileExtension } from '#common/utils/question-handler/questionLanguage.js' 15 | import { getQuestionByMode } from '#common/utils/store/controller/question.js' 16 | import { easyCheckView } from '#common/view/check.view.js' 17 | import { aim } from '#resources/text/aim.js' 18 | import { artFontLogo } from '#resources/text/art-font-logo.js' 19 | import { description } from '#resources/text/description.js' 20 | import { lkExamples } from '#resources/text/examples.js' 21 | import { love } from '#resources/text/love.js' 22 | import select from '@inquirer/select' 23 | import { program } from 'commander' 24 | 25 | const version = process.env.VERSION ?? DefaultVer 26 | program 27 | .version(version) 28 | .description(`${description}\n${artFontLogo}\n${aim}`) 29 | .addHelpText('after', lkExamples + love) 30 | .arguments('[identity]') 31 | .option('-t, --today', 'Check the question today.') 32 | .option('-i, --identity ', 'Check the specified question by identity.') 33 | .option('-r, --random', 'Check the last random question.') 34 | .option('-e, --easy', 'Use easy mode.') 35 | .option('-d, --directory ', 'Set the question directory.') 36 | .option('-l, --language [language]', 'Set/Get the code language of question.') 37 | .option('-v, --ver', 'Check the version info and some extra info about leetcode-practice.') 38 | .option('-u, --update', 'Check the version to determine whether to update to the latest one.') 39 | .parse(process.argv) 40 | 41 | const cmdArgs = program.args 42 | const cmdOpts = program.opts() 43 | // 获取模式和参数 44 | const mode = referMode(cmdArgs, cmdOpts) 45 | const args = getArgs(mode, cmdArgs, cmdOpts) 46 | // 通用参数执行 47 | const baseDir = await commonMode(cmdOpts, easyCheckView) 48 | // 检测函数 49 | async function check(mode, question) { 50 | if (!question) { 51 | logger.info('题目信息不存在,请使用lc指令进行创建~') 52 | return false 53 | } 54 | const filePath = path.join(baseDir, getQuestionFileName(question), `question${getQuestionFileExtension(question?.lang)}`) 55 | if (!fs.existsSync(filePath)) { 56 | logger.info(`文件[${filePath}]不存在,请确保已经创建!`) 57 | } 58 | else { 59 | logger.info(`MODE: ${mode}\n题目[${getQuestionChineseName(question)}]检查结果:`) 60 | await checkQuestionByPath(filePath) 61 | } 62 | return true 63 | } 64 | // 模式对应的action 65 | const callModeAction = { 66 | today: async () => { 67 | const question = await getQuestionByMode('today') 68 | await check('today', question) 69 | process.exit(0) 70 | }, 71 | random: async () => { 72 | const question = await getQuestionByMode('random') 73 | await check('random', question) 74 | process.exit(0) 75 | }, 76 | identity: async (id) => { 77 | let question 78 | if (!id) { 79 | // 如果未指定id说明是要检测模式创建的题目 80 | question = await getQuestionByMode(mode) 81 | await check('identity', question) 82 | } 83 | else { 84 | question = await getFilePathById(id, baseDir) 85 | const needToSelect = { 86 | type: 'list', 87 | name: 'need', 88 | message: `在当前目录下存在id为[${id}]的题目副本,请选择你要检查的副本:`, 89 | choices: [], 90 | } 91 | /** 92 | * 只检查一个题目 93 | * @param fileOrFiles 94 | * @returns {Promise} 95 | */ 96 | const checkOne = async (fileOrFiles) => { 97 | const needToCheck = { 98 | type: 'list', 99 | name: 'check', 100 | message: '当前题目目录中存在多个题目文件副本,请选择一个进行检查:', 101 | choices: [], 102 | default: null, 103 | } 104 | let filePath 105 | switch (typeof fileOrFiles) { 106 | case 'undefined': 107 | logger.warn(`虽然在题目目录中,但当前目录下不存在[${id}]的题目文件!`) 108 | process.exit(0) 109 | break 110 | case 'string': 111 | filePath = fileOrFiles 112 | break 113 | case 'object': 114 | needToCheck.choices = fileOrFiles.map((o) => { 115 | return { name: o, value: o } 116 | }) 117 | needToCheck.default = fileOrFiles?.find(o => o.endsWith(getQuestionFileExtension(DefaultLang))) 118 | filePath = await select(needToCheck) 119 | break 120 | } 121 | return await checkQuestionByPath(filePath) 122 | } 123 | 124 | let files 125 | let which 126 | switch (typeof question) { 127 | case 'undefined': 128 | logger.warn(`当前目录下未找到题目id为[${id}]的题目!`) 129 | process.exit(0) 130 | break 131 | case 'string': 132 | files = getQuestionFileInDir(path.resolve(baseDir, question)) 133 | break 134 | case 'object': 135 | needToSelect.choices = question.map((o) => { 136 | return { name: o, value: o } 137 | }) 138 | which = await select(needToSelect) 139 | files = getQuestionFileInDir(path.resolve(baseDir, which)) 140 | break 141 | } 142 | await checkOne(files) 143 | } 144 | process.exit(0) 145 | }, 146 | } 147 | // 执行指令分发 148 | callModeAction[mode](args) 149 | -------------------------------------------------------------------------------- /common/view/finder.view.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { createQuestionById, createQuestionByTitleSlug } from '#common/utils/cli-utils/createQuestion.js' 3 | import { logger } from '#common/utils/logger/logger.js' 4 | import { getAllQuestionList } from '#common/utils/question-getter/getAllQuestionList.js' 5 | import { getPlanQuestionList } from '#common/utils/question-getter/getPlanQuestionList.js' 6 | import { getQuestionByKeyword } from '#common/utils/question-getter/getQuestionByKeyword.js' 7 | import { getQuestionTagType } from '#common/utils/question-getter/getQuestionTagType.js' 8 | import { getStudyPlanList } from '#common/utils/question-getter/getStudyPlanList.js' 9 | import { getQuestionListCodeBySlug, getQuestionListCodeByTag } from '#common/utils/question-handler/getQuestionListCodeBy.js' 10 | import { getAllQuestion, setAllQuestion } from '#common/utils/store/controller/allQuestion.js' 11 | import input from '@inquirer/input' 12 | import select, { Separator } from '@inquirer/select' 13 | 14 | function handleQuestionList(list) { 15 | const questionList = [] 16 | list.forEach((item) => { 17 | if (!item.premiumOnly && item.name.indexOf('SQL') <= -1 && item.name.indexOf('Pandas') <= -1) { 18 | questionList.push({ 19 | name: `${item.name}(${item.questionNum}题)`, 20 | value: item.slug, 21 | }) 22 | } 23 | }) 24 | return questionList 25 | } 26 | 27 | async function studyMode(baseDir = process.cwd()) { 28 | // const sprintInterviewCompanyList = await getStudyPlanList( 29 | // 'sprint-interview-company' 30 | // ) 31 | const crackingCodingInterviewList = await getStudyPlanList('cracking-coding-interview') 32 | const deepDiveTopicsList = await getStudyPlanList('deep-dive-topics') 33 | const questionList = [ 34 | // ...handleQuestionList(sprintInterviewCompanyList), 35 | // new Separator(), 36 | ...handleQuestionList(crackingCodingInterviewList), 37 | new Separator(), 38 | ...handleQuestionList(deepDiveTopicsList), 39 | ] 40 | const planListMode = { 41 | message: '请选择学习计划', 42 | choices: questionList, 43 | pageSize: 30, 44 | } 45 | const planSlug = await select(planListMode) 46 | const createMode = await select({ 47 | message: '拉题模式', 48 | choices: [ 49 | { name: '单个选择', value: 'single' }, 50 | { name: '全部拉取(不穩定)', value: 'all' }, 51 | ], 52 | }) 53 | if (createMode === 'single') { 54 | const { planSubGroups } = await getPlanQuestionList(planSlug) 55 | const planList = planSubGroups.reduce((acc, cur) => { 56 | acc.push( 57 | ...cur.questions.map((res) => { 58 | return { 59 | cnTitle: res.translatedTitle, 60 | enTitle: res.titleSlug, 61 | } 62 | }), 63 | ) 64 | return acc 65 | }, []) 66 | const singleMode = { 67 | message: '请选择题目?', 68 | choices: planList.map(res => ({ 69 | name: res.cnTitle, 70 | value: res.enTitle, 71 | })), 72 | pageSize: 30, 73 | } 74 | const singleChoice = await select(singleMode) 75 | 76 | await createQuestionByTitleSlug(singleChoice, baseDir) 77 | } 78 | if (createMode === 'all') { 79 | const dir = path.resolve(baseDir, planSlug.toString()) 80 | logger.off() 81 | await getQuestionListCodeBySlug(planSlug, dir) 82 | logger.on() 83 | logger.info(`题目全部拉取完成: file://${dir}`) 84 | } 85 | } 86 | 87 | async function keywordMode(baseDir = process.cwd()) { 88 | const keyword = await input({ message: '请输入关键词', name: 'keyword' }) 89 | const data = await getQuestionByKeyword(keyword) 90 | const list = data?.map((q) => { 91 | return { 92 | name: `${q.frontendQuestionId}.${q.titleCn}`, 93 | value: q.frontendQuestionId, 94 | } 95 | }) 96 | const listQuestion = { 97 | type: 'list', 98 | name: 'chooseQuestion', 99 | message: '请选择题目', 100 | choices: list, 101 | pageSize: 30, 102 | } 103 | const chooseQuestion = await select(listQuestion) 104 | await createQuestionById(chooseQuestion, baseDir) 105 | } 106 | 107 | async function selectMode(baseDir = process.cwd()) { 108 | const questionTagList = await getQuestionTagType() 109 | const tagList = questionTagList.reduce((acc, cur) => { 110 | acc.push( 111 | ...cur.tagRelation.map((res) => { 112 | return { 113 | name: `${res.tag.nameTranslated ? res.tag.nameTranslated : res.tag.name}(${res.questionNum})`, 114 | value: res.tag.slug, 115 | } 116 | }), 117 | ) 118 | return acc 119 | }, []) 120 | 121 | const tagQuestion = { 122 | type: 'list', 123 | name: 'chooseTag', 124 | message: '请选择标签', 125 | choices: tagList, 126 | pageSize: 30, 127 | } 128 | const chooseTag = await select(tagQuestion) 129 | const allQuestion = await getAllQuestion() 130 | // 未发现题目 所以先自动拉取题目 131 | if (!allQuestion?.length) { 132 | logger.info('本地数据库未初始化,自动执行初始化流程,请稍等~') 133 | try { 134 | const allQuestionData = await getAllQuestionList() 135 | await setAllQuestion(allQuestionData) 136 | const newData = await getAllQuestion() 137 | allQuestion.push(...newData) 138 | } 139 | catch (e) { 140 | logger.error('初始化失败!终止.') 141 | process.exit(0) 142 | } 143 | finally { 144 | logger.info('本地数据库初始化完成.') 145 | } 146 | } 147 | const tagQuestionList = allQuestion.filter(question => question.topicTags?.some(topic => topic.slug === chooseTag)) 148 | if (!tagQuestionList?.length) { 149 | logger.info('您选择的类型暂无可拉取题目~') 150 | process.exit(0) 151 | } 152 | const createMode = await select({ 153 | message: '拉题模式', 154 | choices: [ 155 | { name: '单个选择(不穩定)', value: 'single' }, 156 | { name: '全部拉取(不穩定)', value: 'all' }, 157 | ], 158 | }) 159 | if (createMode === 'single') { 160 | const singleMode = { 161 | type: 'list', 162 | name: 'chooseTagQuestion', 163 | message: '请选择题目', 164 | choices: tagQuestionList.map(res => ({ 165 | name: res.translatedTitle, 166 | value: res.questionId, 167 | })), 168 | pageSize: 30, 169 | } 170 | 171 | const singleChoice = await select(singleMode) 172 | await createQuestionById(singleChoice, baseDir) 173 | } 174 | if (createMode === 'all') { 175 | const dir = path.resolve(baseDir, chooseTag.toString()) 176 | logger.off() 177 | await getQuestionListCodeByTag(tagQuestionList, dir) 178 | logger.on() 179 | logger.info(`题目全部拉取完成: file://${dir}`) 180 | } 181 | } 182 | 183 | export async function easyFinderView(baseDir = process.cwd()) { 184 | const choices = [ 185 | { name: '关键词搜索', value: 'keyword', description: '关键词描述' }, 186 | { name: '学习计划', value: 'study', description: '企业和经典面试题目列表' }, 187 | { name: '筛选模式', value: 'select', description: '筛选题目' }, 188 | ] 189 | 190 | const modeQuestion = { 191 | message: '请选择查找的模式?', 192 | choices, 193 | } 194 | const mode = await select(modeQuestion) 195 | 196 | const modeMap = { 197 | study: studyMode, 198 | keyword: keywordMode, 199 | select: selectMode, 200 | } 201 | await modeMap[mode](baseDir) 202 | } 203 | -------------------------------------------------------------------------------- /common/utils/question-handler/questionLanguage.js: -------------------------------------------------------------------------------- 1 | import { DefaultLang } from '#common/constants/question.const.js' 2 | import { getStore, setStore } from '#common/utils/store/controller/store.js' 3 | 4 | /** 5 | * 语言 6 | * @type {*[]} 7 | */ 8 | export const LANGUAGES = [ 9 | { 10 | id: 0, 11 | name: 'cpp', 12 | extension: '.cpp', 13 | blockComment: '/*\n*\n*/', // 块级注释 使用换行符来分割 14 | lineComment: '//', // 行级注释 不需要分割 15 | }, 16 | { 17 | id: 1, 18 | name: 'java', 19 | extension: '.java', 20 | blockComment: '/*\n*\n*/', 21 | lineComment: '//', 22 | }, 23 | { 24 | id: 2, 25 | name: 'python', 26 | extension: '.py', 27 | blockComment: '\'\'\'\n\n\'\'\'', 28 | lineComment: '#', 29 | }, 30 | { 31 | id: 11, 32 | name: 'python3', 33 | extension: '.py', 34 | blockComment: '\'\'\'\n\n\'\'\'', 35 | lineComment: '#', 36 | }, 37 | { 38 | id: 3, 39 | name: 'mysql', 40 | extension: '.sql', 41 | blockComment: '/*\n*\n*/', 42 | lineComment: '--', 43 | }, 44 | { 45 | id: 14, 46 | name: 'mssql', 47 | extension: '.sql', 48 | blockComment: '/*\n*\n*/', 49 | lineComment: '--', 50 | }, 51 | { 52 | id: 15, 53 | name: 'oraclesql', 54 | extension: '.sql', 55 | blockComment: '/*\n*\n*/', 56 | lineComment: '--', 57 | }, 58 | { 59 | id: 4, 60 | name: 'c', 61 | extension: '.c', 62 | blockComment: '/*\n*\n*/', 63 | lineComment: '//', 64 | }, 65 | { 66 | id: 5, 67 | name: 'csharp', 68 | extension: '.cs', 69 | blockComment: '/*\n*\n*/', 70 | lineComment: '//', 71 | }, 72 | { 73 | id: 6, 74 | name: 'javascript', 75 | extension: '.js', 76 | blockComment: '/*\n*\n*/', 77 | lineComment: '//', 78 | }, 79 | { 80 | id: 20, 81 | name: 'typescript', 82 | extension: '.ts', 83 | blockComment: '/*\n*\n*/', 84 | lineComment: '//', 85 | }, 86 | { 87 | id: 8, 88 | name: 'bash', 89 | extension: '.sh', 90 | blockComment: '\'\'\'\n\n\'\'\'', 91 | lineComment: '#', 92 | }, 93 | { 94 | id: 19, 95 | name: 'php', 96 | extension: '.php', 97 | blockComment: '/*\n*\n*/', 98 | lineComment: '//', 99 | }, 100 | { 101 | id: 9, 102 | name: 'swift', 103 | extension: '.swift', 104 | blockComment: '/*\n*\n*/', 105 | lineComment: '//', 106 | }, 107 | { 108 | id: 13, 109 | name: 'kotlin', 110 | extension: '.kt', 111 | blockComment: '/*\n*\n*/', 112 | lineComment: '//', 113 | }, 114 | { 115 | id: 24, 116 | name: 'dart', 117 | extension: '.dart', 118 | blockComment: '/*\n*\n*/', 119 | lineComment: '//', 120 | }, 121 | { 122 | id: 10, 123 | name: 'golang', 124 | extension: '.go', 125 | blockComment: '/*\n*\n*/', 126 | lineComment: '//', 127 | }, 128 | { 129 | id: 7, 130 | name: 'ruby', 131 | extension: '.rb', 132 | blockComment: '=begin\n\n=end', 133 | lineComment: '#', 134 | }, 135 | { 136 | id: 12, 137 | name: 'scala', 138 | extension: '.scala', 139 | blockComment: '/*\n*\n*/', 140 | lineComment: '//', 141 | }, 142 | { 143 | id: 16, 144 | name: 'html', 145 | extension: '.html', 146 | blockComment: '', 147 | lineComment: '', 148 | }, 149 | { 150 | id: 17, 151 | name: 'pythonml', 152 | extension: '.py', 153 | blockComment: '\'\'\'\n\n\'\'\'', 154 | lineComment: '#', 155 | }, 156 | { 157 | id: 18, 158 | name: 'rust', 159 | extension: '.rs', 160 | blockComment: '/*\n*\n*/', 161 | lineComment: '//', 162 | }, 163 | { 164 | id: 21, 165 | name: 'racket', 166 | extension: '.rkt', 167 | blockComment: ';;\n\n;;', 168 | lineComment: '', 169 | }, 170 | { 171 | id: 22, 172 | name: 'erlang', 173 | extension: '.erl', 174 | blockComment: '%%\n\n%%', 175 | lineComment: '', 176 | }, 177 | { 178 | id: 23, 179 | name: 'elixir', 180 | extension: '.ex', 181 | blockComment: '#\n#\n#', 182 | lineComment: '', 183 | }, 184 | { 185 | id: 25, 186 | name: 'pythondata', 187 | extension: '.py', 188 | blockComment: '\'\'\'\n\n\'\'\'', 189 | lineComment: '#', 190 | }, 191 | { 192 | id: 26, 193 | name: 'react', 194 | extension: '.jsx', 195 | blockComment: '/*\n*\n*/', 196 | lineComment: '//', 197 | }, 198 | { 199 | id: 27, 200 | name: 'vanillajs', 201 | extension: '.js', 202 | blockComment: '/*\n*\n*/', 203 | lineComment: '//', 204 | }, 205 | { 206 | id: 28, 207 | name: 'postgresql', 208 | extension: '.sql', 209 | blockComment: '/*\n*\n*/', 210 | lineComment: '--', 211 | }, 212 | ] 213 | 214 | /** 215 | * 设置编程主语言 216 | */ 217 | export async function setQuestionLanguage(lang = DefaultLang) { 218 | await setStore('language', lang) 219 | } 220 | /** 221 | * 获取当前语言 - 默认语言是JS 222 | */ 223 | export async function getQuestionLanguage() { 224 | const lang = await getStore('language') 225 | return lang ?? DefaultLang 226 | } 227 | 228 | /** 229 | * 获取语言对象中的一个 230 | * @param lang 231 | * @returns {*} 232 | */ 233 | export function getLang(lang) { 234 | return LANGUAGES.find(o => o.name.toLowerCase() === lang.toLowerCase()) 235 | } 236 | /** 237 | * 获取文件后缀 238 | * @param lang 239 | */ 240 | export function getQuestionFileExtension(lang = DefaultLang) { 241 | const detail = getLang(lang) 242 | return detail?.extension 243 | } 244 | 245 | /** 246 | * 通过后缀获取语言配置 247 | * @param extensionLike 248 | * @returns {*} 249 | */ 250 | export function getLangByExtension(extensionLike) { 251 | const reg = /\.\w+$/m 252 | const match = extensionLike.match(reg) 253 | const extension = match === null ? `.${extensionLike}` : match[0] 254 | return LANGUAGES?.find(o => o.extension === extension) 255 | } 256 | /** 257 | * 获取行注释 258 | * @param lang 259 | * @returns {*} 260 | */ 261 | export function getLineComment(lang = DefaultLang) { 262 | const langObj = getLang(lang) 263 | if (langObj.lineComment !== '') { 264 | return langObj.lineComment 265 | } 266 | else { 267 | // 因为保底是都有块级注释的 268 | return null 269 | } 270 | } 271 | 272 | /** 273 | * 获取块级注释 274 | * @param lang 275 | * @returns {*} 276 | */ 277 | export function getBlockComment(lang = DefaultLang) { 278 | const langObj = getLang(lang) 279 | return langObj.blockComment 280 | } 281 | 282 | /** 283 | * 设置行注释 284 | * @param lang 285 | * @param comment 286 | * @returns {*} 287 | */ 288 | export function setLineComment(lang = DefaultLang, comment = '') { 289 | const lineComment = getLineComment(lang) 290 | if (lineComment !== null) { 291 | const lines = comment.split('\n') 292 | return lines.reduce((p, line) => (p += `${lineComment} ${line}\n`), '') 293 | } 294 | else { 295 | return setBlockComment(lang, comment) 296 | } 297 | } 298 | 299 | /** 300 | * 设置块注释 301 | * @param lang 302 | * @param comment 303 | * @returns {*} 304 | */ 305 | export function setBlockComment(lang = DefaultLang, comment = '') { 306 | const block = getBlockComment(lang) 307 | const splitter = block.split('\n') 308 | const lines = comment.split('\n') 309 | switch (splitter.length) { 310 | case 3: { 311 | const start = splitter[0] 312 | const end = splitter[2] 313 | const startTag = splitter[1] 314 | const content = lines.reduce((p, line) => (p += `${startTag} ${line}\n`), '') 315 | return `${start}\n${content}${end}\n` 316 | } 317 | default: 318 | return comment 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Leetcode practice [![npm](https://img.shields.io/npm/v/leetcode-practice.svg)](https://www.npmjs.com/package/leetcode-practice) [![build status](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml) 2 | 3 | **中文** · [English](./README.md) · [日本語](./README_JP.md) 4 | 5 | ## I.项目信息 6 | 7 | ### 1.简介 8 | 9 | 一句话介绍:“在编辑器中开始练习你的`leetcode`每日一题!” 10 | 11 | 如果,你想要在编辑器中编写你的题解... 12 | 13 | 如果,你想要简单快速的获取每日一题... 14 | 15 | 如果,你想要创建你自己的题解仓库... 16 | 17 | 那么,`leetcode-practice`将满足你的一切想要! 18 | 19 | ### 2.预览 20 | 21 | ![CLI-lc](./resources/images/lc-cli-h.png) 22 | 23 | ## II.使用须知 24 | 25 | ### 0. 预备条件 26 | 27 | | 工具 | 备注 | 28 | | -------- | :------------------: | 29 | | nodejs | lts | 30 | | git | lts | 31 | | patience | 一颗能够坚持刷题的心 | 32 | 33 | ### 1. 我可以怎么用? (三种方案供你选择) 34 | 35 | #### 方案A.脚手架 CLI (推荐) 36 | 37 | 此方案是最为推荐的使用方案,通过脚手架指令`lc`、`lk`、`lf`自由灵活的在任意的目录中创建并检查你的题解。 38 | 39 | > 使用预览 40 | 41 | #### 方案B.模板项目 Template project (支持) 42 | 43 | 如果你想要快速的创建一个自己的题解库,可以使用我们的模板项目进行快速的创建github项目,并且获得完善的初始化内容。 44 | 45 | > 使用预览 46 | 47 | #### 方案C.插件 Plugin (支持,待开发) 48 | 49 | 如果你想在编辑器中通过点击操作按钮来创建题解,可以使用我们的编辑器插件(计划支持`WebStorm`和`VSCode`)在你的编辑器中创建题解。 50 | 51 | > 使用预览 52 | 53 | ### 2. 我应该怎么用?(三种方案的详细的安装和使用教程) 54 | 55 | #### 方案A.脚手架 CLI 56 | 57 | ##### 1.安装 58 | 59 | 你可以使用任意一款npm包管理软件(例如`npm`,`yarn`,`pnpm`等)的在`项目`中或者`全局`进行安装。 60 | 61 | ```shell 62 | # 示例:全局安装 63 | # 使用npm进行全局安装 64 | npm install -g leetcode-practice 65 | # 使用pnpm进行安装 66 | pnpm install -g leetcode-practice 67 | # 使用yarn进行安装 68 | yarn global install leetcode-practice 69 | 70 | # 示例:在项目中安装 71 | yarn add --dev leetcode-practice 72 | ``` 73 | 74 | > 提示: 在项目中安装和全局安装的区别在于脚手架的作用范围不同。如果在项目中安装,那么指令只能在该项目内使用,而在其他项目中(未安装leetcode-practice包的情况下)则无法使用指令。而全局安装则允许在任何目录下使用指令。 75 | 76 | ##### 2.使用 77 | 78 | 在这里给大家简单介绍一下每日一题的`创建`和`检查`,还有关键词搜索的使用方式,最为详细具体的指令和参数请参照[KFC及其关键参数说明 ](#3-kfc及其关键参数说明-三种方案通用的参考手册) 79 | 80 | ###### 2.1 创建题目 81 | 82 | 在指令作用范围中,执行指令`lc`来进行创建今天的每日一题: 83 | 84 | ```shell 85 | # 移动到我的工作目录中 86 | cd my-workspace 87 | # 创建每日一题 88 | lc 89 | ``` 90 | 91 | 当看到提示: 92 | 93 | ```shell 94 | MODE: today 95 | 题目[299.猜数字游戏]获取成功! 96 | 题目文件地址为:/my-workspace/299.bulls-and-cows/question.js 97 | ``` 98 | 99 | 你的题目就已经创建完成了!可以在编辑器中愉快的解题了! 100 | 101 | ###### 2.2 检查题目 102 | 103 | 当你完成解题代码的编写,可以通过`lk`指令进行简单的检查题解! 104 | 105 | ```shell 106 | # lk指令和lc指令的模式是对应的,不带参数的时候意为检查今日题目 107 | lk 108 | ``` 109 | 110 | 你会得到如下提示信息: 111 | 112 | ```shell 113 | MODE: today 114 | 题目[299.猜数字游戏]检测结果: 115 | ┌────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┬────────────┬────────────┐ 116 | │ 测试结果 │ 预期结果 │ 执行结果 │ 执行用时 │ 内存占用 │ 117 | ├────────────┼──────────────────────────────────────────┼──────────────────────────────────────────┼────────────┼────────────┤ 118 | │ 通过 │ "1A3B" │ "1A3B" │ 0.1361ms │ 2.79 KB │ 119 | │ 通过 │ "1A1B" │ "1A1B" │ 0.0623ms │ 2.93 KB │ 120 | └────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┴────────────┴────────────┘ 121 | 点击跳转到题目提交: https://leetcode.cn/problems/bulls-and-cows/ 122 | ``` 123 | 124 | 你可以看到`测试结果`、`预期结果`、`执行结果`、`执行用时`以及`内存占用`信息。 125 | 126 | ###### 2.3 搜索题目 127 | 128 | 关键词搜索是核心指令`lf`的一项基本功能,可以通过关键词的形式获取题目信息,并快速的选择创建。 129 | 130 | ```shell 131 | # lf指令是一个完全交互式的指令 按其中的提示进行输入操作即可获取自己想要的信息 132 | lf 133 | ``` 134 | 135 | 以下演示获取`两数之和`这个题目如何通过关键词搜索创建: 136 | 137 | ```shell 138 | # 在输入关键词 两数 之后 按下回车 会出现所有和两数相关的题目 按键盘的上下键进行选择 回车进行确认 139 | ? 请选择查找的模式? 关键词搜索 140 | ? 请输入关键词 两数 141 | ? 请选择题目 142 | LCR 025.两数相加 II 143 | 2.两数相加 144 | 29.两数相除 145 | ❯ 1.两数之和 146 | LCR 006.两数之和 II - 输入有序数组 147 | 445.两数相加 II 148 | LCR 056.两数之和 IV - 输入二叉搜索树 149 | (Use arrow keys to reveal more choices) 150 | 151 | # 确认后的会进行题目创建,返回成功信息 152 | ? 请选择查找的模式? 关键词搜索 153 | ? 请输入关键词 两数 154 | ? 请选择题目 1.两数之和 155 | 1 156 | MODE: identity 157 | 题目[1.两数之和]获取成功! 158 | 题目文件地址为:fill:///my-workspace/1.two-sum/question.js:36 159 | ``` 160 | 161 | > 提示: 在创建完成的时候会输出一个可以点击跳转的文件地址,如果在编辑器的控制台中点击,会直接打开对应的文件的函数开始位置。 162 | 163 | #### 方案B.模板项目 Template project 164 | 165 | ##### 1.创建模板项目 166 | 167 | 1. 在`github`中打开我们的模板项目[leetcodePracticeTemplate](https://github.com/EternalHeartTeam/LeetcodePracticeTemplate)。 168 | 2. `点击`右上角的`Use this template`,选择[`Create a new repository`](https://github.com/new?template_name=LeetcodePracticeTemplate&template_owner=EternalHeartTeam)。 169 | 3. 就像创建一个正常的仓库一样去填写信息即可。 170 | 4. 等待...然后完成,从此你就有了一个自己的`leetcode题解仓库`,并且长期受`leetcode-practice`官方支持! 171 | 172 | > 填充创建过程图 173 | 174 | ##### 2. 使用模板项目 175 | 176 | ###### 1. 拉取项目,并进行初始化依赖。 177 | 178 | e.g. 以我个人的项目为例 179 | 180 | ```shell 181 | # 这里只是示例,请拉取自己的项目(在你看到这进行尝试的时候,此项目可能已经被清理,正常现象请勿疑惑) 182 | git clone git@github.com:wh131462/my-leetcode-practice.git 183 | # 移动进项目目录 184 | cd my-leetcode-practice 185 | # 初始化 186 | npm i 187 | ``` 188 | 189 | ###### 2. 在项目中的使用 190 | 191 | 在模板项目中使用`leetcode-practice`的方式有两种,一种是`项目内指令`的形式,一种是`npm脚本`的形式。接下来演示创建的过程,其他的指令用法和参数与脚手架一致,请参考[KFC及其关键参数说明](#3-kfc及其关键参数说明-三种方案通用的参考手册)部分。 192 | 193 | ```shell 194 | # 在项目的根目录下 执行npm run lc 创建今日一题,因为默认配置了 -d src 所以 会在 src 目录下进行创建 195 | npm run lc 196 | # 也可以使用 yarn :任意一种你喜欢的包管理工具 197 | yarn lc 198 | ``` 199 | 200 | 你也可以使用指令`lc`来创建,当然,你只能在项目中使用我们的脚本(如果你没有全局安装`leetcode-practice`的话)。 201 | 202 | ```shell 203 | # 使用lc 会在当前工作目录创建 如果 需要和脚本的表现保持一致(指在src目录下创建),请使用 -d src 参数 204 | lc 205 | ``` 206 | 207 | > 注意:在这里进行一下强调,项目内指令的说法是指指令的作用范围限制是当前的项目目录,也就是说在其他的目录,你尝试使用lc指令会发现不起作用。 208 | > 209 | > 同样的,你也会发现在第一次安装好的时候,在项目内使用lc指令,也是不起作用的,这个时候需要关闭终端(terminal)再打开进行手动的刷新缓存。 210 | > 211 | > npm脚本指在 package.json 中的 scripts 字段下封装的脚本指令。 212 | 213 | ###### 3. 更新依赖 214 | 215 | 当你想要进行更新的时候,可以执行封装好的npm指令:`update`,可以帮你安装`最新版本(latest)`的`leetcode-practice`。 216 | 217 | ```shell 218 | # 使用任意一种包管理工具执行即可 219 | npm run update 220 | # 当然你也可以自己执行指令 221 | npm i -D leetcode-practice 222 | ``` 223 | 224 | #### 方案C.插件 Plugin (待开发) 225 | 226 | ### 3. KFC及其关键参数说明 (三种方案通用的参考手册) 227 | 228 | #### [0].什么是KFC? 229 | 230 | `KFC`是一个简便记法,可以快速记忆我们的三个核心指令:`lk`,`lf`,`lc`。 231 | 232 | | 指令 | 说明 | 233 | | ---- | ------------------------------------------------------------------------------------------------------ | 234 | | lk | 核心检查指令,支持三种模式对应的题目检测 | 235 | | lf | 核心查找指令,可以快捷的搜索你想要的题目,支持Hot100,关键词搜索,条件筛选等模式进行搜索题目并支持创建 | 236 | | lc | 核心创建指令,支持三种创建模式 ( 每日一题、指定题目、随机题目 ) 进行题目的创建 | 237 | 238 | #### [1].lk 239 | 240 | | 简单参数 | 完整参数 | 说明 | 241 | | ------------------------ | ----------------------- | ---------------------------------------------------------------------- | 242 | | 无参数/`-t` | `--today` | 检查今日的每日一题 | 243 | | 题目编号/`-i ` | `--identity ` | 检查指定编号对应的题目,会检查当前工作目录下的是否存在指定id对应的题目 | 244 | | `-r` | `--random` | 检查上一次使用随机模式创建的题目 | 245 | | `-e` | `--easy` | 交互式的根据提示去检查对应的题目 | 246 | 247 | > 注意: 当你检测的时候需要注意,使用什么模式去检查,请确保你已经执行过对应模式的创建操作。 248 | > 指定编号模式除外,因为会优先检查指定的id是否在当前工作目录存在。 249 | 250 | #### [2].lf 251 | 252 | | 简单参数 | 完整参数 | 说明 | 253 | | -------- | -------- | -------------------------------------------------- | 254 | | 无参数 | 无 | 进入交互式查询,可根据提示搜索或筛选题目并创建题目 | 255 | 256 | #### [3].lc 257 | 258 | | 简单参数 | 完整参数 | 说明 | 259 | | ------------------------ | ----------------------- | -------------------------------- | 260 | | 无参数/`-t` | `--today` | 创建今日的每日一题 | 261 | | 题目编号/`-i ` | `--identity ` | 创建指定编号对应的题目 | 262 | | `-r` | `--random` | 创建当前目录下未出现过的随机题目 | 263 | | `-e` | `--easy` | 交互式创建题目 | 264 | | `-a` | `--all` | 获取全部题目缓存,用于lf指令 | 265 | 266 | #### [4].通用参数 267 | 268 | | 简单参数 | 完整参数 | 说明 | 269 | | ---------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 270 | | `-d ` | `--directory ` | 指定工作目录(是一个当前执行目录的相对地址),会影响创建和检查 | 271 | | `-V` | `--version` | 检查版本号 | 272 | | `-v` | `--ver` | 检查版本号信息并且输出一些额外的信息 | 273 | | `-h` | `--help` | 获取帮助信息 | 274 | | `-l [language]` | `--language [language]` | 不指定参数为获取当前的语言环境(默认为javascript),指定参数可以设置语言环境为对应语言(如`-l java`可以指定语言环境为java) | 275 | | `-u` | `--update` | 更新当前脚本或者依赖 | 276 | 277 | > 注意:通用参数指三个脚本`lk`,`lf`,`lc`都支持的参数,使用上的语义一般来说是相同的,但是可能有些指令上的行为含义会有所不同(比如`lc`指令指定`-d`参数是指`在指定目录中创建题目`,而`lk`指令指定`-d`参数就是指`在指定目录创建题目`)。 278 | 279 | ## III.其他信息 280 | 281 | ### 1. 贡献者们 282 | 283 | 项目的开发和完善离不开这些贡献者的辛勤付出,在此真诚感谢各位大佬的付出! 284 | 285 | 286 | 287 | ### 2.如何贡献 288 | 289 | 如果你也有一颗热爱开源的心,想要为我们的开源事业贡献一份力量,那么请参考我们的[贡献手册](./.github/CONTRIBUTING.md)。 290 | 291 | ### 3.使用反馈 292 | 293 | 如果你有使用上的问题需要解惑,或者一些好的建议想要提出,可以加我们的使用反馈群进行反馈! 294 | 295 | 在群里和开发者面对面的交流,希望我们能产生共鸣,迸发出新的火花! 296 | 297 | ![反馈群](./resources/images/service-qrcode.jpg) 298 | 299 | ### 4.Star趋势图 300 | 301 | [![Star History Chart](https://api.star-history.com/svg?repos=EternalHeartTeam/leetcode-practice&type=Date)](https://star-history.com/#EternalHeartTeam/leetcode-practice&Date) 302 | -------------------------------------------------------------------------------- /README_JP.md: -------------------------------------------------------------------------------- 1 | # Leetcode practice [![npm](https://img.shields.io/npm/v/leetcode-practice.svg)](https://www.npmjs.com/package/leetcode-practice) [![build status](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml) 2 | 3 | [中文](./README_CN.md) · [English](./README.md) · **日本語** 4 | 5 | ## I.プロジェクト情報 6 | 7 | ### 1.概要 8 | 9 | 一文で述べると、「エディタで毎日の`leetcode`問題を練習し始めましょう!」 10 | 11 | もし、エディタで問題を解きたい場合... 12 | 13 | もし、簡単で迅速に毎日の問題を取得したい場合... 14 | 15 | もし、自分の解答リポジトリを作成したい場合... 16 | 17 | そのすべてができるのが`leetcode-practice`です! 18 | 19 | ### 2.プレビュー 20 | 21 | ![CLI-lc](./resources/images/lc-cli-h.png) 22 | 23 | ## II.使用上の注意 24 | 25 | ### 0.前提条件 26 | 27 | | ツール | 備考 | 28 | | ------ | :----------------------: | 29 | | nodejs | lts | 30 | | git | lts | 31 | | 我慢 | 問題を解き続けるための心 | 32 | 33 | ### 1. どう使えるのか? (3つの選択肢) 34 | 35 | #### オプションA.スクリプトCLI (おすすめ) 36 | 37 | この方法は、自由自在に`lc`、`lk`、`lf`のスクリプトコマンドを使用して、任意のディレクトリで問題を作成およびチェックするためにお勧めされています。 38 | 39 | > 使用例 40 | 41 | #### オプションB.テンプレートプロジェクト (サポート) 42 | 43 | 自分の解答リポジトリを素早く作成したい場合は、当社のテンプレートプロジェクトを使用して、GitHubプロジェクトを素早く作成し、初期化されたコンテンツを取得できます。 44 | 45 | > 使用例 46 | 47 | #### オプションC.プラグイン (サポート、開発中) 48 | 49 | エディタで問題を作成するためにクリック操作を使用したい場合は、当社のエディタプラグイン(予定では`WebStorm`および`VSCode`をサポート)を使用して、エディタで問題を作成できます。 50 | 51 | > 使用例 52 | 53 | ### 2. どうすればいいですか?(3つの選択肢の詳細なインストールおよび使用手順) 54 | 55 | #### オプションA.スクリプトCLI 56 | 57 | ##### 1.インストール 58 | 59 | `lc`、`lk`、`lf`のスクリプトを使用して、任意のnpmパッケージ管理ソフトウェア(たとえば*`npm`*、_`yarn`_、*`pnpm`*など)で`プロジェクト`または`グローバル`にインストールできます。 60 | 61 | ```shell 62 | # 例:グローバルインストール 63 | # npmを使用してグローバルにインストール 64 | npm install -g leetcode-practice 65 | # pnpmを使用してインストール 66 | pnpm install -g leetcode-practice 67 | # yarnを使用してインストール 68 | yarn global install leetcode-practice 69 | 70 | # 例:プロジェクト内のインストール 71 | yarn add --dev leetcode-practice 72 | ``` 73 | 74 | > ヒント: プロジェクト内およびグローバルインストールの違いは、スクリプトの範囲が異なることです。プロジェクト内でインストールすると、スクリプトはそのプロジェクト内でのみ使用できますが、他のプロジェクトでは(leetcode-practiceパッケージがインストールされていない場合)スクリプトを使用できません。一方、グローバルインストールでは、どのディレクトリでもスクリプトを使用できます。 75 | 76 | ##### 2.使用 77 | 78 | ここでは、毎日の`作成`および`チェック`、およびキーワード検索の方法を簡単に説明します。詳細なスクリプトとパラメータについては、[KFCとその重要なパラメータの説明](#3-kfcとその重要なパラメータの説明-3つのオプション共通のリファレンスガイド)を参照してください。 79 | 80 | ###### 2.1 問題の作成 81 | 82 | スクリプトの範囲内で、指定の日の毎日の問題を作成するには、`lc`を実行します。 83 | 84 | ```shell 85 | # 私の作業ディレクトリに移動する 86 | cd my-workspace 87 | # 毎日の問題を作成する 88 | lc 89 | ``` 90 | 91 | 次のプロンプトが表示されると: 92 | 93 | ```shell 94 | MODE: today 95 | 問題[299.猜数字游戏]を取得しました! 96 | 問題ファイルの場所:/my-workspace/299.bulls-and-cows/question.js 97 | ``` 98 | 99 | 問題は作成されました!エディタで問題を解く準備ができました! 100 | 101 | ###### 2.2 問題のチェック 102 | 103 | 問題の解答コードを書き終えたら、`lk`スクリプトを使用して問題の簡単なチェックを行うことができます! 104 | 105 | ```shell 106 | # lkスクリプトはlcスクリプトと対応しています。パラメータなし 107 | 108 | の場合、今日の問題をチェックすることを意味します 109 | lk 110 | ``` 111 | 112 | 以下のようなメッセージが表示されます: 113 | 114 | ```shell 115 | MODE: today 116 | 問題[299.猜数字游戏]のテスト結果: 117 | ┌────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┬────────────┬────────────┐ 118 | │ テスト結果 │ 期待結果 │ 実行結果 │ 実行時間 │ メモリ使用量 │ 119 | ├────────────┼──────────────────────────────────────────┼──────────────────────────────────────────┼────────────┼────────────┤ 120 | │ パス │ "1A3B" │ "1A3B" │ 0.1361ms │ 2.79 KB │ 121 | │ パス │ "1A1B" │ "1A1B" │ 0.0623ms │ 2.93 KB │ 122 | └────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┴────────────┴────────────┘ 123 | 問題提出へのリンクをクリックしてください: https://leetcode.cn/problems/bulls-and-cows/ 124 | ``` 125 | 126 | `テスト結果`、`期待結果`、`実行結果`、`実行時間`、`メモリ使用量`の情報が表示されます。 127 | 128 | ###### 2.3 問題の検索 129 | 130 | キーワード検索は、`lf`スクリプトの主要な機能の1つです。キーワードの形式で問題情報を取得し、簡単に作成できます。 131 | 132 | ```shell 133 | # lfスクリプトは完全なインタラクティブなスクリプトで、プロンプトに従って入力操作を行うことで、希望する情報を簡単に取得できます 134 | lf 135 | ``` 136 | 137 | 以下は、`two-sum`問題をキーワードで検索して作成する方法の例です。 138 | 139 | ```shell 140 | # キーワード "two sum" を入力し、Enterキーを押すと、すべての"two sum"に関連する問題が表示されます。キーボードの上下キーを使用して選択し、Enterキーを押して確認します。 141 | ? 検索モードを選択してください? キーワード検索 142 | ? キーワードを入力してください 二つの数 143 | ? 問題を選んでください 1.两数之和 144 | 1 145 | MODE: identity 146 | 問題[1.两数之和]を取得しました! 147 | 問題ファイルの場所:fill:///my-workspace/1.two-sum/question.js:36 148 | ``` 149 | 150 | > 注意: 作成時に、クリック可能なファイルパスが表示され、エディタのコンソールでクリックすると、対応するファイルの関数の開始位置が直接開きます。 151 | 152 | #### オプションB.テンプレートプロジェクト 153 | 154 | ##### 1.テンプレートプロジェクトの作成 155 | 156 | 1. GitHubでテンプレートプロジェクト[leetcodePracticeTemplate](https://github.com/EternalHeartTeam/LeetcodePracticeTemplate)を開きます。 157 | 2. 右上隅の`Use this template`をクリックし、[`Create a new repository`](https://github.com/new?template_name=LeetcodePracticeTemplate&template_owner=EternalHeartTeam)を選択します。 158 | 3. 通常のリポジトリの作成と同様に情報を入力します。 159 | 4. しばらく待ちます...そして完了です。これで、自分の`leetcode解答リポジトリ`ができました! 160 | 161 | > 作成プロセスの準備画像 162 | 163 | ##### 2. テンプレートプロジェクトの使用 164 | 165 | ###### 1. プロジェクトをクローンし、依存関係を初期化します。 166 | 167 | 例: 個人的なプロジェクトを使用します。 168 | 169 | ```shell 170 | # これは例です。自分のプロジェクトをクローンしてください。(あなたがこれを試している時点で、このプロジェクトはもうクリアされているかもしれませんが、正常な現象なので疑わないでください) 171 | git clone git@github.com:wh131462/my-leetcode-practice.git 172 | # プロジェクトディレクトリに移動 173 | cd my-leetcode-practice 174 | # 初期化 175 | npm i 176 | ``` 177 | 178 | ###### 2. プロジェクト内での使用 179 | 180 | テンプレートプロジェクトでは、`leetcode-practice`を使用する方法は2つあります。1つは`プロジェクト内コマンド`の形式、もう1つは`npmスクリプト`の形式です。詳細な使い方とパラメータについては、[KFCとその重要なパラメータの説明](#3-kfcとその重要なパラメータの説明-3つのオプション共通のリファレンスガイド)を参照してください。 181 | 182 | ```shell 183 | # ルートディレクトリで実行するnpm run lc コマンドを使用して、今日の問題を作成します。デフォルトで -d src を設定しているため、src ディレクトリ内で作成されます 184 | npm run lc 185 | # または yarn: 好きなどちらかのパッケージ管理ツールを使用できます 186 | yarn lc 187 | ``` 188 | 189 | あなたはまた、`lc`コマンドを使用して作成することもできますが、あなたはプ 190 | 191 | ロジェクト内でしかスクリプトを使用できません(あなたが`leetcode-practice`をグローバルにインストールしていない場合)。 192 | 193 | ```shell 194 | # ここでの注意点として、プロジェクト内コマンドとは、コマンドの作用範囲が現在のプロジェクトディレクトリに限定されることを意味します。つまり、他のディレクトリで lc コマンドを試しても効果はありません。 195 | # 同様に、最初にインストールしたときに、プロジェクト内で lc コマンドを使用しても、そのコマンドが機能しない場合があります。この場合は、ターミナル(terminal)を閉じて再度開いて、キャッシュを手動で更新する必要があります。 196 | # npmスクリプトは、package.jsonのscriptsフィールドにエンベロープされたスクリプトコマンドのことです。 197 | 198 | # lcコマンドを使用して作成することもできますが、その場合はプロジェクト内でしか使用できません(あなたが"leetcode-practice"をグローバルにインストールしていない場合)。 199 | lc 200 | ``` 201 | 202 | > 注意: ここで強調すると、プロジェクト内コマンドは、コマンドの作用範囲が現在のプロジェクトディレクトリに限定されることを意味します。つまり、他のディレクトリで `lc` コマンドを試しても効果はありません。同様に、最初にインストールしたときに、プロジェクト内で `lc` コマンドを使用しても、そのコマンドが機能しない場合があります。この場合は、ターミナル(terminal)を閉じてから再度開き、キャッシュを手動で更新する必要があります。npmスクリプトは、package.jsonのscriptsフィールドにエンベロープされたスクリプトコマンドのことです。 203 | 204 | ###### 3. 依存関係の更新 205 | 206 | 更新したい場合は、`update`という名前の封印されたnpmコマンドを使用して、`最新バージョン(latest)`の`leetcode-practice`をインストールできます。 207 | 208 | ```shell 209 | # どちらかのパッケージ管理ツールを使用して実行できます 210 | npm run update 211 | # もちろん、コマンドを手動で実行することもできます 212 | npm i -D leetcode-practice 213 | ``` 214 | 215 | #### オプションC.プラグイン(開発中) 216 | 217 | ### 3. KFCとその重要なパラメータの説明 (3つのオプション共通のリファレンスガイド) 218 | 219 | #### [0].KFCとは? 220 | 221 | `KFC`は、当社の3つの主要コマンド`lk`、`lf`、`lc`を素早く覚えるための簡単な記憶法です。 222 | 223 | | コマンド | 説明 | 224 | | -------- | ------------------------------------------------------------------------------------------------------------------------- | 225 | | lk | 主要なチェックコマンドであり、3つのモードに対応する問題のチェックを行います | 226 | | lf | 主要な検索コマンドであり、Hot100、キーワード検索、条件検索などのモードで問題を検索し、問題を作成します | 227 | | lc | 主要な作成コマンドであり、3つの作成モード(毎日の問題、指定された問題、ランダムな問題)をサポートし、問題の作成を行います | 228 | 229 | #### [1]. lk (問題のチェック) 230 | 231 | | 簡易パラメータ | 完全パラメータ | 説明 | 232 | | ------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | 233 | | 無し/`-t` | `--today` | 今日の毎日の問題をチェックします。 | 234 | | 問題番号/`-i ` | `--identity ` | 指定された番号に対応する問題をチェックします。指定されたIDが現在の作業ディレクトリに存在するかどうかも確認します。 | 235 | | `-r` | `--random` | 最後に使用されたランダムモードで作成された問題をチェックします。 | 236 | | `-e` | `--easy` | インタラクティブにプロンプトに従って問題をチェックします。 | 237 | 238 | > 注意: チェックする際は、どのモードを使用するかに注意してください。対応するモードの作成操作をすでに実行したことを確認してください。 239 | > 指定番号モードは例外であり、作成されていない問題をチェックすることもできます。 240 | 241 | #### [2]. lf (問題の検索) 242 | 243 | | 簡易パラメータ | 完全パラメータ | 説明 | 244 | | -------------- | -------------- | -------------------------------------------------------------------------------------------------------- | 245 | | 無し | 無し | インタラクティブにクエリに入り、ヒントに従って問題を検索したりフィルタリングしたりして問題を作成します。 | 246 | 247 | #### [3]. lc (問題の作成) 248 | 249 | | 簡易パラメータ | 完全パラメータ | 説明 | 250 | | ------------------------ | ----------------------- | -------------------------------------------- | 251 | | 無し/`-t` | `--today` | 今日の毎日の問題を作成します。 | 252 | | 問題番号/`-i ` | `--identity ` | 指定された番号に対応する問題を作成します。 | 253 | | `-r` | `--random` | まだ現れていないランダムな問題を作成します。 | 254 | | `-e` | `--easy` | インタラクティブに問題を作成します。 | 255 | 256 | #### [4]. 汎用パラメータ 257 | 258 | | 簡易パラメータ | 完全パラメータ | 説明 | 259 | | ---------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 260 | | `-d ` | `--directory ` | 作業ディレクトリを指定します(現在の実行ディレクトリの相対パス)。これは作成とチェックの両方に影響を与えます。 | 261 | | `-V` | `--version` | バージョン番号を確認します。 | 262 | | `-v` | `--ver` | Leetcode-practice のバージョン情報と追加情報をチェックする | 263 | | `-h` | `--help` | ヘルプ情報を取得します。 | 264 | | `-l [language]` | `--language [language]` | パラメータなしで現在の言語環境を取得します(デフォルトはJavaScript)。言語を指定すると、その言語の環境が設定されます(たとえば、`-l java`でJavaの環境が設定されます)。 | 265 | | `-u` | `--update` | 現在のスクリプトまたは依存関係を更新します。 | 266 | 267 | > 注意: 汎用パラメータは`lk`,`lf`,`lc`の3つのスクリプトでサポートされていますが、その使用法の意味論は一般的には同じですが、いくつかの指示には異なる挙動が含まれる場合があります(たとえば、`lc`コマンドで`-d`パラメータを指定した場合は、`指定したディレクトリ内で問題を作成`するのに対し、`lk`コマンドで`-d`パラメータを指定すると`指定したディレクトリに問題をチェック`します)。 268 | 269 | ## III. その他の情報 270 | 271 | ### 1. 貢献者たち 272 | 273 | このプロジェクトの開発と改善には、これらの貢献者のご努力が不可欠です。ここに、すべての方々に心から感謝いたします! 274 | 275 | 276 | 277 | ### 2. 貢献方法 278 | 279 | もしオープンソースに対する情熱を持ち、私たちのオープンソースイニシアチブに貢献したいとお考えでしたら、[貢献ガイドライン](./.github/CONTRIBUTING.md) を参照してください。 280 | 281 | ### 3. 使用フィードバック 282 | 283 | 使用上の問題がある場合や、提案がある場合は、使用フィードバックグループにご参加ください! 284 | 285 | グループで開発者と対面してコミュニケーションを取り、新たなアイデアを生み出すことを願っています! 286 | 287 | ![フィードバックグループ](./resources/images/service-qrcode.jpg) 288 | 289 | ### 4. Starのトレンドチャート 290 | 291 | [![Star History Chart](https://api.star-history.com/svg?repos=EternalHeartTeam/leetcode-practice&type=Date)](https://star-history.com/#EternalHeartTeam/leetcode-practice&Date) 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leetcode practice [![npm](https://img.shields.io/npm/v/leetcode-practice.svg)](https://www.npmjs.com/package/leetcode-practice) [![build status](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/EternalHeartTeam/leetcode-practice/actions/workflows/ci.yml) 2 | 3 | [中文](./README_CN.md) · **English** ·[日本語](./README_JP.md) 4 | 5 | ## I. Project Information 6 | 7 | ### 1. Introduction 8 | 9 | One-sentence introduction: "Start practicing your `LeetCode` daily questions in the editor!" 10 | 11 | If you want to write your solutions in the editor... 12 | 13 | If you want a simple and quick way to get daily questions... 14 | 15 | If you want to create your own repository for solutions... 16 | 17 | Then, `leetcode-practice` is all you need! 18 | 19 | ### 2. Preview 20 | 21 | ![CLI-lc](./resources/images/lc-cli-h.png) 22 | 23 | ## II. Instructions for Use 24 | 25 | ### 0. Prerequisites 26 | 27 | | Tool | Remarks | 28 | | -------- | :---------------------------------------------: | 29 | | nodejs | lts | 30 | | git | lts | 31 | | patience | A heart that can persist in practicing problems | 32 | 33 | ### 1. How Can I Use It? (Three Options for You to Choose From) 34 | 35 | #### Option A. Command-Line Interface (CLI) (Recommended) 36 | 37 | This option is the most recommended way to use the tool. You can freely create and check your solutions in any directory using the CLI commands `lc`, `lk`, and `lf`. 38 | 39 | > Usage Preview 40 | 41 | #### Option B. Template Project (Supported) 42 | 43 | If you want to quickly create your own repository for solutions, you can use our template project to quickly create a GitHub project and get comprehensive initialization content. 44 | 45 | > Usage Preview 46 | 47 | #### Option C. Plugin (Supported, Under Development) 48 | 49 | If you want to create solutions in your editor by clicking buttons, you can use our editor plugin (planned support for `WebStorm` and `VSCode`) to create solutions in your editor. 50 | 51 | > Usage Preview 52 | 53 | ### 2. How Should I Use It? (Detailed Installation and Usage Guides for Three Options) 54 | 55 | #### Option A. Command-Line Interface (CLI) 56 | 57 | ##### 1. Installation 58 | 59 | You can install it globally or locally in your project using any npm package manager (e.g., `npm`, `yarn`, `pnpm`). 60 | 61 | ```shell 62 | # Example: Global Installation 63 | # Using npm for global installation 64 | npm install -g leetcode-practice 65 | # Using pnpm for installation 66 | pnpm install -g leetcode-practice 67 | # Using yarn for installation 68 | yarn global install leetcode-practice 69 | 70 | # Example: Installation in the Project 71 | yarn add --dev leetcode-practice 72 | ``` 73 | 74 | > Note: The difference between installing in the project and globally lies in the scope of the CLI commands. If installed in the project, the commands can only be used within that project, and cannot be used in other projects (where the `leetcode-practice` package is not installed). Global installation allows you to use the commands in any directory. 75 | 76 | ##### 2. Usage 77 | 78 | Here's a simple introduction to creating and checking daily questions, as well as using keyword search. For more detailed instructions and parameters, please refer to the [KFC and its Key Parameter Explanation](#3-kfc-and-its-key-parameter-explanation-universal-reference-manual) section. 79 | 80 | ###### 2.1 Creating a Question 81 | 82 | Within the command scope, use the command `lc` to create today's daily question: 83 | 84 | ```shell 85 | # Move to my workspace directory 86 | cd my-workspace 87 | # Create the daily question 88 | lc 89 | ``` 90 | 91 | When you see the prompt: 92 | 93 | ```shell 94 | MODE: today 95 | 题目[299.猜数字游戏]获取成功! 96 | 题目文件地址为:/my-workspace/299.bulls-and-cows/question.js 97 | ``` 98 | 99 | Your question has been created! You can now solve it happily in the editor! 100 | 101 | ###### 2.2 Checking a Question 102 | 103 | Once you've written your solution code, you can use the `lk` command to perform a simple check of the solution! 104 | 105 | ```shell 106 | # The lk command corresponds to the lc command mode, and when used without parameters, it means checking today's question 107 | lk 108 | ``` 109 | 110 | You will receive information like this: 111 | 112 | ```shell 113 | MODE: today 114 | 题目[299.猜数字游戏]检测结果: 115 | ┌────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┬────────────┬────────────┐ 116 | │ 测试结果 │ 预期结果 │ 执行结果 │ 执行用时 │ 内存占用 │ 117 | ├────────────┼──────────────────────────────────────────┼──────────────────────────────────────────┼────────────┼────────────┤ 118 | │ 通过 │ "1A3B" │ "1A3B" │ 0.1361ms │ 2.79 KB │ 119 | │ 通过 │ "1A1B" │ "1A1B" │ 0.0623ms │ 2.93 KB │ 120 | └────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┴────────────┴────────────┘ 121 | Click here to submit the question: https://leetcode-cn.com/problems/bulls-and-cows/ 122 | ``` 123 | 124 | You can see the `Test Result`, `Expected Result`, `Execution Result`, `Execution Time`, and `Memory Usage` information. 125 | 126 | ###### 2.3 Searching for a Question 127 | 128 | Keyword search is a basic function of the core command `lf`, which allows you to quickly search for the question you want and choose to create it. 129 | 130 | ```shell 131 | # The lf command is completely interactive. Follow the prompts to enter the desired information 132 | lf 133 | ``` 134 | 135 | The following demonstrates how to use keyword search to create the `Two Sum` question: 136 | 137 | ```shell 138 | # After entering the keyword `two sum` and pressing Enter, all questions related to `two sum` will appear. Use the up and down arrow keys on the keyboard to select, and press Enter to confirm. 139 | ? Choose the search mode? Keyword search 140 | ? Enter keywords two sum 141 | ? Choose a question 142 | LCR 025.两数相加 II 143 | 2.两数相加 144 | 29.两数相除 145 | ❯ 1.两数之和 146 | LCR 006.两数之和 II - 输入有序数组 147 | 445.两数相加 II 148 | LCR 056.两数之和 IV - 输入二叉搜索树 149 | (Use arrow keys to reveal more choices) 150 | 151 | # After confirmation, the question will be created, and a success message will be returned 152 | ? Choose the search mode? Keyword search 153 | ? Enter keywords two sum 154 | ? Choose a question 1.两数之和 155 | 1 156 | MODE: identity 157 | 题目[1.两数之和]获取成功! 158 | 题目文件地址为:fill:///my-workspace/1.two-sum/question.js:36 159 | ``` 160 | 161 | > Note: When the creation is complete, a clickable file address will be output. If clicked in the editor console, it will directly open the corresponding file to the start of the function. 162 | 163 | #### Option B. Template Project 164 | 165 | ##### 1. Creating a Template Project 166 | 167 | 1. Open 168 | 169 | our template project [leetcodePracticeTemplate](https://github.com/EternalHeartTeam/LeetcodePracticeTemplate) on GitHub. 2. Click on `Use this template` in the upper right corner and select `Create a new repository`. 3. Fill in the information as you would when creating a normal repository. 4. Wait... and you're done. You now have your own `LeetCode solutions repository` and will receive long-term support from the `leetcode-practice` official team! 170 | 171 | > Fill in the creation process chart 172 | 173 | ##### 2. Using the Template Project 174 | 175 | ###### 1. Clone the project and initialize the dependencies. 176 | 177 | e.g. Using my personal project as an example 178 | 179 | ```shell 180 | # This is just an example. Please clone your own project (when you attempt this, the project may have been cleared, which is a normal phenomenon, please don't be surprised) 181 | git clone git@github.com:wh131462/my-leetcode-practice.git 182 | # Move into the project directory 183 | cd my-leetcode-practice 184 | # Initialize 185 | npm i 186 | ``` 187 | 188 | ###### 2. Usage in the project 189 | 190 | There are two ways to use the `leetcode-practice` in the template project: `project internal commands` and `npm scripts`. The process of creation is demonstrated below, and other command usages and parameters are consistent with the scaffold. Please refer to the [KFC and its Key Parameter Explanation](#3-kfc-and-its-key-parameter-explanation-universal-reference-manual) section. 191 | 192 | ```shell 193 | # Under the root directory of the project, execute npm run lc to create today's question. Because -d src is configured by default, the creation will be done under the src directory 194 | npm run lc 195 | # You can also use yarn: any package management tool you like 196 | yarn lc 197 | ``` 198 | 199 | You can also use the `lc` command to create, of course, you can only use our script in the project (if you haven't installed the `leetcode-practice` package globally). 200 | 201 | ```shell 202 | # Using lc will create in the current working directory. If you need to keep consistent with the script's behavior (creating under src directory), please use the -d src parameter 203 | lc 204 | ``` 205 | 206 | > Note: Here's an emphasis. The term "project internal command" means that the scope of the command is limited to the current project directory, meaning that if you try to use the lc command in another directory, you'll find that it doesn't work. Similarly, you'll find that when you first install it, the lc command doesn't work in the project until you close and reopen the terminal (terminal) to manually refresh the cache. npm script refers to a script command encapsulated under the scripts field in package.json. 207 | 208 | ###### 3. Updating Dependencies 209 | 210 | When you want to update, you can execute the encapsulated npm command `update`, which can help you install the `latest version (latest)` of `leetcode-practice`. 211 | 212 | ```shell 213 | # Execute using any package management tool 214 | npm run update 215 | # Of course, you can also execute the command yourself 216 | npm i -D leetcode-practice 217 | ``` 218 | 219 | #### Option C. Plugin (Under Development) 220 | 221 | ### 3. KFC and its Key Parameter Explanation (Universal Reference Manual) 222 | 223 | #### [0]. What is KFC? 224 | 225 | `KFC` is a simple mnemonic that can quickly remember our three core commands: `lk`, `lf`, and `lc`. 226 | 227 | | Command | Explanation | 228 | | ------- | --------------------------------------------------------------------------------------------------------------------------------- | 229 | | lk | Core checking command, supports three modes corresponding to the question for checking questions | 230 | | lf | Core search command, which can quickly search for the question you want based on prompts | 231 | | lc | Core creation command, supports three creation modes (daily question, specified question, random question) for creating questions | 232 | 233 | #### [1]. lk 234 | 235 | | Short Parameter | Full Parameter | Explanation | 236 | | --------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | 237 | | No parameter / `-t` | `--today` | Check today's daily question | 238 | | Question number / `-i ` | `--identity ` | Check the question corresponding to the specified number, and check if the question with the specified ID exists in the current working directory | 239 | | `-r` | `--random` | Check the question created using the random mode last time | 240 | | `-e` | `--easy` | Interactive check of the corresponding question based on prompts | 241 | 242 | > Note: When checking, please pay attention to what mode to use for checking, and make sure that you have performed the corresponding mode creation operation. 243 | > The specified ID mode is an exception because it will check whether the specified ID exists in the current working directory. 244 | 245 | #### [2]. lf 246 | 247 | | Short Parameter | Full Parameter | Explanation | 248 | | --------------- | -------------- | ------------------------------------------------------------------------- | 249 | | No parameter | None | Enter interactive search, and search or filter questions based on prompts | 250 | 251 | #### [3]. lc 252 | 253 | | Short Parameter | Full Parameter | Explanation | 254 | | --------------------------------- | ----------------------- | --------------------------------------------------------------------------------------- | 255 | | No parameter / `-t` | `--today` | Create today's daily question | 256 | | Question number / `-i ` | `--identity ` | Create the question corresponding to the specified number | 257 | | `-r` | `--random` | Create a random question that has not appeared in the current directory | 258 | | `-e` | `--easy` | Interactive creation of questions | 259 | | `-a` | `--all` | Fetch all questions from server and store them locally, prepare database for lf command | 260 | 261 | #### [4]. General Parameters 262 | 263 | | Short Parameter | Full Parameter | Explanation | 264 | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 265 | | `-d ` | `--directory ` | Specify the working directory (a relative address of the current execution directory), which will affect the creation and checking | 266 | | `-V` | `--version` | Check the version number | 267 | | `-v` | `--ver` | Check the version info and some extra info about leetcode-practice | 268 | | `-h` | `--help` | Get help information | 269 | | `-l [language]` | `--language [language]` | Without specifying a parameter, get the current language environment (default is JavaScript). Specifying a parameter can set the language environment to the corresponding language (e.g., `-l java` sets the language environment to Java) | 270 | | `-u` | `--update` | Update the current script or dependency | 271 | 272 | > Note: General parameters refer to parameters supported by all three scripts `lk`, `lf`, and `lc`. The semantics of usage are generally the same, but there may be differences in the meanings of behaviors in some commands (for example, specifying the `-d` parameter in the `lc` command means `creating questions in the specified directory`, while in the `lk` command, specifying the `-d` parameter means `creating questions in the specified directory`). 273 | 274 | ## III. Additional Information 275 | 276 | ### 1. Contributors 277 | 278 | The development and improvement of the project would not be possible without the hard work of these contributors. Sincere thanks to all of them! 279 | 280 | 281 | 282 | ### 2. How to Contribute 283 | 284 | If you share a passion for open source and would like to contribute to our open source initiative, please refer to our [contribution guidelines](./.github/CONTRIBUTING.md). 285 | 286 | ### 3. Feedback 287 | 288 | If you have any questions about usage or would like to offer some suggestions, feel free to join our feedback group! 289 | 290 | Engage in face-to-face discussions with developers in the group, hoping to resonate with each other and spark new ideas! 291 | 292 | ![Feedback Group](./resources/images/service-qrcode.jpg) 293 | 294 | ### 4. Star Trend Chart 295 | 296 | [![Star History Chart](https://api.star-history.com/svg?repos=EternalHeartTeam/leetcode-practice&type=Date)](https://star-history.com/#EternalHeartTeam/leetcode-practice&Date) 297 | --------------------------------------------------------------------------------