From 4ff0d4cc5aecb725a92af08f700eb621ace29eb1 Mon Sep 17 00:00:00 2001 From: sorrycc Date: Thu, 7 Nov 2024 11:26:03 +0800 Subject: [PATCH] feat: tnf generate --- .changeset/rich-lobsters-fold.md | 5 +++ README.md | 3 +- package.json | 3 ++ pnpm-lock.yaml | 59 +++++++++++++++++++++++++++---- src/cli.ts | 54 ++++++++++++++++------------ src/generate.ts | 60 ++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 .changeset/rich-lobsters-fold.md create mode 100644 src/generate.ts diff --git a/.changeset/rich-lobsters-fold.md b/.changeset/rich-lobsters-fold.md new file mode 100644 index 0000000..8eac206 --- /dev/null +++ b/.changeset/rich-lobsters-fold.md @@ -0,0 +1,5 @@ +--- +'@umijs/tnf': patch +--- + +feat: tnf generate page diff --git a/README.md b/README.md index acc78d2..24bfc92 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,10 @@ $ npx serve dist -s ## Commands -> WIP. More commands will be added in the future. - - `tnf create --template=`: Create a new project with the given template. - `tnf build`: Build the project. - `tnf dev`: Start the development server. +- `tnf generate `: Generate a new page (or component and other types in the future). - `tnf preview`: Preview the product after building the project. ## API diff --git a/package.json b/package.json index d87534b..ce63802 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/express-http-proxy": "^1.6.6", + "@types/fs-extra": "^11.0.4", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@types/spdy": "^3.4.9", @@ -46,10 +47,12 @@ "cors": "^2.8.5", "express": "^4.21.1", "express-http-proxy": "^2.1.1", + "fs-extra": "^11.1.0", "get-port-please": "^3.1.2", "http-proxy-middleware": "^3.0.3", "pathe": "^1.1.2", "picocolors": "^1.1.1", + "random-color": "^1.0.1", "react": "19.0.0-rc-02c0e824-20241028", "react-dom": "19.0.0-rc-02c0e824-20241028", "sirv": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f78b7d..11a51f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@types/express-http-proxy': specifier: ^1.6.6 version: 1.6.6 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 '@types/react': specifier: ^18.3.12 version: 18.3.12 @@ -71,6 +74,9 @@ importers: express-http-proxy: specifier: ^2.1.1 version: 2.1.1 + fs-extra: + specifier: ^11.1.0 + version: 11.2.0 get-port-please: specifier: ^3.1.2 version: 3.1.2 @@ -83,6 +89,9 @@ importers: picocolors: specifier: ^1.1.1 version: 1.1.1 + random-color: + specifier: ^1.0.1 + version: 1.0.1 react: specifier: 19.0.0-rc-02c0e824-20241028 version: 19.0.0-rc-02c0e824-20241028 @@ -2520,8 +2529,6 @@ packages: dependencies: '@types/jsonfile': 6.1.4 '@types/node': 22.9.0 - dev: true - optional: true /@types/hapi__joi@17.1.9: resolution: {integrity: sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==} @@ -2562,8 +2569,6 @@ packages: requiresBuild: true dependencies: '@types/node': 22.9.0 - dev: true - optional: true /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -3685,20 +3690,49 @@ packages: string-width: 7.2.0 dev: true + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: false + /clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} dev: false + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@0.3.0: + resolution: {integrity: sha512-sz29j1bmSDfoAxKIEU6zwoIZXN6BrFbAMIhfYCNyiZXBDuU/aiHlN84lp/xDzL2ubyFhLDobHIlU1X70XRrMDA==} + dependencies: + color-name: 1.1.4 + dev: false + + /color@0.11.4: + resolution: {integrity: sha512-Ajpjd8asqZ6EdxQeqGzU5WBhhTfJ/0cA4Wlbre7e5vXfmDSmda7Ov6jeKoru+b0vHcb1CqvuroTHp5zIWzhVMA==} + dependencies: + clone: 1.0.4 + color-convert: 1.9.3 + color-string: 0.3.0 + dev: false + /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true @@ -4940,6 +4974,15 @@ packages: universalify: 2.0.1 dev: true + /fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: false + /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -5716,7 +5759,6 @@ packages: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - dev: true /less-plugin-resolve@1.0.2: resolution: {integrity: sha512-e1AHq0XNTU8S3d9JCc8CFYajoUBr0EK3pcuLT5PogyBBeE0knzZJL105kKKSZWfq2lQLq3/uEDrMK3JPq+fHaA==} @@ -7150,6 +7192,12 @@ packages: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: true + /random-color@1.0.1: + resolution: {integrity: sha512-6IV6i3HHKluwo6GmRtgestd1nABtFrwUrmJ2rQpymwQt8/X2ZoC7z8SWxeF9KC8FoBFEAuHpwWI76uJ19+2j8A==} + dependencies: + color: 0.11.4 + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -8742,7 +8790,6 @@ packages: /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - dev: true /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} diff --git a/src/cli.ts b/src/cli.ts index 826b961..75a32cd 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,35 +7,51 @@ import { setNoDeprecation, setNodeTitle, } from './fishkit/node.js'; +import { generate } from './generate'; -interface RunOptions { - cwd: string; - name?: string; - template?: string; -} - -async function run(cmd: string, options: RunOptions) { +async function run(cwd: string) { + const argv = yargsParser(process.argv.slice(2), { + alias: { + g: 'generate', + }, + }); + const cmd = argv._[0]; + assert(cmd, 'Command is required'); switch (cmd) { case 'create': const { create } = await import('./create.js'); - return create(options); + return create({ + cwd: cwd, + name: argv.name, + template: argv.template, + }); case 'build': const { build } = await import('./build.js'); return build({ - cwd: options.cwd, - config: await loadConfig({ cwd: options.cwd }), + cwd, + config: await loadConfig({ cwd }), }); case 'dev': const { dev } = await import('./dev.js'); return dev({ - cwd: options.cwd, - config: await loadConfig({ cwd: options.cwd }), + cwd, + config: await loadConfig({ cwd }), }); case 'preview': const { preview } = await import('./preview.js'); return preview({ - cwd: options.cwd, - config: await loadConfig({ cwd: options.cwd }), + cwd, + config: await loadConfig({ cwd }), + }); + case 'generate': + const type = argv._[1] as string; + const name = argv._[2] as string; + assert(type, 'Type is required'); + assert(name, 'Name is required'); + return generate({ + cwd, + type, + name, }); default: throw new Error(`Unknown command: ${cmd}`); @@ -46,15 +62,7 @@ setNoDeprecation(); checkVersion(MIN_NODE_VERSION); setNodeTitle(FRAMEWORK_NAME); -const argv = yargsParser(process.argv.slice(2)); -const cmd = argv._[0]; - -assert(cmd, 'Command is required'); -run(cmd as string, { - cwd: process.cwd(), - name: argv._[1] as string | undefined, - template: argv.template as string | undefined, -}).catch((err) => { +run(process.cwd()).catch((err) => { console.error(err.message); process.exit(1); }); diff --git a/src/generate.ts b/src/generate.ts new file mode 100644 index 0000000..4b5ead1 --- /dev/null +++ b/src/generate.ts @@ -0,0 +1,60 @@ +import assert from 'assert'; +import fs from 'fs-extra'; +import path from 'path'; +// @ts-ignore +import randomColor from 'random-color'; + +interface GenerateOptions { + cwd: string; + type: string; + name: string; +} + +export function generate(opts: GenerateOptions) { + if (opts.type === 'page') { + return generatePage(opts); + } +} + +function generatePage(opts: GenerateOptions) { + const pagesDir = path.join(opts.cwd, 'src/pages'); + const pageName = opts.name; + const pagePath = path.join(pagesDir, `${pageName}.tsx`); + const styleModulePath = path.join(pagesDir, `${pageName}.module.less`); + + assert( + !fs.existsSync(pagePath) && !fs.existsSync(styleModulePath), + `Page ${pageName} already exists.`, + ); + + fs.ensureDirSync(pagesDir); + + const componentName = pageName.charAt(0).toUpperCase() + pageName.slice(1); + const pageContent = `import React from 'react'; +import { createFileRoute } from '@umijs/tnf/router'; +import styles from './${pageName}.module.less'; + +export const Route = createFileRoute('/${pageName}')({ + component: ${componentName}, +}); + +function ${componentName}() { + return ( +
+

Welcome to ${componentName} Page!

+
+ ); +} +`; + + const styleContent = `.container { + color: ${randomColor().hexString()}; +} +`; + + fs.writeFileSync(pagePath, pageContent); + fs.writeFileSync(styleModulePath, styleContent); + + console.log(`Generated page at: ${pagePath}`); + console.log(`Generated styles at: ${styleModulePath}`); +}