diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4b706bd5b..0ea06854e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -72,6 +72,7 @@ jobs: id: changesets uses: changesets/action@v1 with: + version: npx changeset version && pnpm i publish: npx changeset publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/cli/src/create/index.ts b/packages/cli/src/create/index.ts new file mode 100644 index 000000000..d92f1d6c5 --- /dev/null +++ b/packages/cli/src/create/index.ts @@ -0,0 +1,10 @@ +import path from 'node:path'; +import { copyFiles, TEMPLATES_DIR } from '../utils.js'; + +const TEMPLATE_REACT = path.join(TEMPLATES_DIR, 'react'); + +export async function create(): Promise { + const dest = path.join(process.cwd(), 'farm-react'); + + copyFiles(TEMPLATE_REACT, dest); +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 1399604e1..cb0905643 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,5 +1,6 @@ import { start, build } from '@farmfe/core'; import { cac } from 'cac'; +import { create } from './create/index.js'; import { COMMANDS } from './plugin/index.js'; const cli = cac(); @@ -23,6 +24,10 @@ cli.command('build', 'Compile the project in production mode').action(() => { }); }); +cli.command('create', 'Create a new project').action(() => { + create(); +}); + cli.command('').action(() => { cli.outputHelp(); }); diff --git a/packages/cli/src/plugin/create.ts b/packages/cli/src/plugin/create.ts index 8fa58c59a..a9464f166 100644 --- a/packages/cli/src/plugin/create.ts +++ b/packages/cli/src/plugin/create.ts @@ -1,9 +1,7 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import path from 'path'; -import walk from 'walkdir'; import inquirer from 'inquirer'; import chalk from 'chalk'; -import { fileURLToPath } from 'url'; +import { copyFiles, TEMPLATES_DIR } from '../utils.js'; export interface CreateArgs { npmName?: string; @@ -11,10 +9,7 @@ export interface CreateArgs { dir?: string; } -const TEMPLATES_DIR = path.join( - path.dirname(fileURLToPath(import.meta.url)), - '../../templates/rust-plugin' -); +const TEMPLATE_PLUGIN = path.join(TEMPLATES_DIR, 'rust-plugin'); const TEMPLATE_NPM_NAME = ''; const TEMPLATE_STRUCT_NAME = ''; @@ -56,22 +51,10 @@ export async function create(args: CreateArgs): Promise { const dest = path.join(process.cwd(), dir); - walk(TEMPLATES_DIR, { sync: true }, (p, stat) => { - if (stat.isFile()) { - const content = readFileSync(p).toString(); - const newContent = content - .replace(new RegExp(TEMPLATE_NPM_NAME, 'g'), npmName) - .replace(new RegExp(TEMPLATE_STRUCT_NAME, 'g'), structName); - - const relativePath = path.relative(TEMPLATES_DIR, p); - const destPath = path.join(dest, relativePath); - - if (!existsSync(path.dirname(destPath))) { - mkdirSync(path.dirname(destPath), { recursive: true }); - } - - writeFileSync(destPath, newContent); - } + copyFiles(TEMPLATE_PLUGIN, dest, (content) => { + return content + .replace(new RegExp(TEMPLATE_NPM_NAME, 'g'), npmName) + .replace(new RegExp(TEMPLATE_STRUCT_NAME, 'g'), structName); }); console.log(chalk.green(`Plugin created successfully in ${dest}`)); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts new file mode 100644 index 000000000..f5403d657 --- /dev/null +++ b/packages/cli/src/utils.ts @@ -0,0 +1,32 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import walkdir from 'walkdir'; + +export const TEMPLATES_DIR = path.join( + path.dirname(fileURLToPath(import.meta.url)), + '..', + 'templates' +); + +export function copyFiles( + source: string, + dest: string, + callback?: (content: string) => string +): void { + walkdir(source, { sync: true }, (p, stat) => { + if (stat.isFile()) { + const content = readFileSync(p).toString(); + const newContent = callback?.(content) ?? content; + + const relativePath = path.relative(source, p); + const destPath = path.join(dest, relativePath); + + if (!existsSync(path.dirname(destPath))) { + mkdirSync(path.dirname(destPath), { recursive: true }); + } + + writeFileSync(destPath, newContent); + } + }); +} diff --git a/packages/cli/templates/react/farm.config.ts b/packages/cli/templates/react/farm.config.ts new file mode 100644 index 000000000..cef6f3eea --- /dev/null +++ b/packages/cli/templates/react/farm.config.ts @@ -0,0 +1,19 @@ +import { defineFarmConfig } from '@farmfe/core/dist/node/config'; + +export default defineFarmConfig({ + compilation: { + input: { + index: './index.html', + }, + resolve: { + symlinks: true, + mainFields: ['module', 'main', 'customMain'], + }, + output: { + path: './build', + }, + }, + server: { + hmr: true, + }, +}); diff --git a/packages/cli/templates/react/index.html b/packages/cli/templates/react/index.html new file mode 100644 index 000000000..10b7c653e --- /dev/null +++ b/packages/cli/templates/react/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + \ No newline at end of file diff --git a/packages/cli/templates/react/package.json b/packages/cli/templates/react/package.json new file mode 100644 index 000000000..ef1dfacdd --- /dev/null +++ b/packages/cli/templates/react/package.json @@ -0,0 +1,18 @@ +{ + "name": "@farmfe-examples/react", + "version": "0.0.0", + "dependencies": { + "react": "18", + "react-dom": "18" + }, + "devDependencies": { + "@farmfe/cli": "*", + "@farmfe/core": "*", + "@types/react": "18", + "@types/react-dom": "18", + "react-refresh": "^0.14.0" + }, + "scripts": { + "start": "farm start" + } +} diff --git a/packages/cli/templates/react/src/comps/counter-button/index.css b/packages/cli/templates/react/src/comps/counter-button/index.css new file mode 100644 index 000000000..abc5bc6bf --- /dev/null +++ b/packages/cli/templates/react/src/comps/counter-button/index.css @@ -0,0 +1,20 @@ +.counter-button { + width: 200px; + height: 50px; + border: none; + background-color: green; + color: white; + font-size: 30px; + line-height: 50px; + border-radius: 10px; + cursor: pointer; +} + +.counter-button:hover { + background-color: lightgreen; +} + +.disable { + background-color: #999; + color: #333; +} \ No newline at end of file diff --git a/packages/cli/templates/react/src/comps/counter-button/index.tsx b/packages/cli/templates/react/src/comps/counter-button/index.tsx new file mode 100644 index 000000000..3a65f54f2 --- /dev/null +++ b/packages/cli/templates/react/src/comps/counter-button/index.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useState } from 'react'; + +import './index.css' + +const COUNT_DOWN = 60; +const BUTTON_TEXT = 'Start Count'; + +export function CounterButton() { + const [count, setCount] = useState(COUNT_DOWN); + const [text, setText] = useState(BUTTON_TEXT); + const [timer, setTimer] = useState(null as null | number); + const [pause, setPause] = useState(false); + + useEffect(() => { + return () => { + if (timer) { + clearInterval(timer); + } + } + }, [timer]); + + const countdown = () => { + console.log(timer, pause, count, text); + setCount(count => { + if (count == 0) { + clearInterval(timer as number); + setText(BUTTON_TEXT); + return 0; + } + + setText(`${count - 1}`); + return count - 1; + }); + }; + + return +} diff --git a/packages/cli/templates/react/src/comps/description/index.tsx b/packages/cli/templates/react/src/comps/description/index.tsx new file mode 100644 index 000000000..c17f87a63 --- /dev/null +++ b/packages/cli/templates/react/src/comps/description/index.tsx @@ -0,0 +1,8 @@ +import React from 'react'; + +export function Description() { + return
+

Farm is a supper fast building engine written in rust. 🔥

+

Visit https://github.com/farm-fe/farm for details

+
+} \ No newline at end of file diff --git a/packages/cli/templates/react/src/index.tsx b/packages/cli/templates/react/src/index.tsx new file mode 100644 index 000000000..b967e22f0 --- /dev/null +++ b/packages/cli/templates/react/src/index.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; + +import { Main } from './main'; +const container = document.querySelector('#root')!; +const root = createRoot(container); + +root.render(
) \ No newline at end of file diff --git a/packages/cli/templates/react/src/main.css b/packages/cli/templates/react/src/main.css new file mode 100644 index 000000000..e6514df5f --- /dev/null +++ b/packages/cli/templates/react/src/main.css @@ -0,0 +1,23 @@ +#root { + background-color: whitesmoke; + font-size: 24px; + height: 300px; +} + +.button-wrapper { + padding-top: 100px; + /* color: red; */ + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: center; +} + +.description { + margin-top: 10px; + color: #333; + font-size: 16px; + margin: 0; + padding: 0; + text-align: center; +} \ No newline at end of file diff --git a/packages/cli/templates/react/src/main.tsx b/packages/cli/templates/react/src/main.tsx new file mode 100644 index 000000000..f53b31404 --- /dev/null +++ b/packages/cli/templates/react/src/main.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { CounterButton } from './comps/counter-button'; +import { Description } from './comps/description'; +import './main.css'; + +export function Main() { + return ( + <> +
+ +
+
+ +
+ + ); +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index f51e9b0cb..e9f8a18a1 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -6,6 +6,7 @@ "declaration": false }, "exclude": ["node_modules"], + "include": ["src/**/*"], "references": [ { "path": "../core/tsconfig.build.json" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81ab171ae..a18f44240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,7 +71,7 @@ importers: packages/cli: specifiers: - '@farmfe/core': workspace:^0.0.0 + '@farmfe/core': workspace:^0.1.0 '@types/inquirer': ^9.0.3 cac: ^6.7.14 chalk: ^5.2.0 @@ -90,7 +90,7 @@ importers: packages/core: specifiers: - '@farmfe/runtime': workspace:^0.0.0 + '@farmfe/runtime': workspace:^0.1.0 '@farmfe/runtime-plugin-hmr': workspace:* '@napi-rs/cli': ^2.10.0 '@swc/helpers': ^0.4.9