diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 10c28cd3..698a0d49 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1961,9 +1961,15 @@ importers: '@flowgram.ai/ts-config': specifier: workspace:* version: link:../../../config/ts-config + '@types/inquirer': + specifier: 9.0.7 + version: 9.0.7 '@types/lodash': specifier: ^4.14.137 version: 4.17.13 + '@types/node': + specifier: ^18 + version: 18.19.68 '@types/react': specifier: ^18 version: 18.3.16 diff --git a/packages/materials/form-materials/bin/index.js b/packages/materials/form-materials/bin/index.ts similarity index 68% rename from packages/materials/form-materials/bin/index.js rename to packages/materials/form-materials/bin/index.ts index 48768f5d..9ab45be6 100755 --- a/packages/materials/form-materials/bin/index.js +++ b/packages/materials/form-materials/bin/index.ts @@ -1,30 +1,31 @@ -#!/usr/bin/env node +#!/usr/bin/env -S npx tsx@latest -import chalk from 'chalk'; -import { Command } from 'commander'; import inquirer from 'inquirer'; +import { Command } from 'commander'; +import chalk from 'chalk'; -import { bfsMaterials, copyMaterial, listAllMaterials } from './materials.js'; -import { getProjectInfo, installDependencies } from './project.js'; +import { getProjectInfo, installDependencies, ProjectInfo } from './project.js'; +import { bfsMaterials, copyMaterial, listAllMaterials, Material } from './materials.js'; const program = new Command(); program - .version('1.0.0') + .version('1.0.1') .description('Add official materials to your project') .argument('[materialName]', 'Optional material name to skip selection (format: type/name)') - .action(async (materialName) => { + .action(async (materialName?: string) => { + // materialName can be undefined console.log(chalk.bgGreenBright('Welcome to @flowgram.ai/form-materials CLI!')); - const projectInfo = getProjectInfo(); + const projectInfo: ProjectInfo = getProjectInfo(); console.log(chalk.bold('Project Info:')); console.log(chalk.black(` - Flowgram Version: ${projectInfo.flowgramVersion}`)); console.log(chalk.black(` - Project Path: ${projectInfo.projectPath}`)); - const materials = listAllMaterials(); + const materials: Material[] = listAllMaterials(); - let material; + let material: Material | undefined; // material can be undefined // Check if materialName is provided and exists in materials if (materialName) { @@ -42,7 +43,9 @@ program // If material not found or materialName not provided, prompt user to select if (!material) { // User select one component - const result = await inquirer.prompt([ + const result = await inquirer.prompt<{ + material: Material; // Specify type for prompt result + }>([ { type: 'list', name: 'material', @@ -57,18 +60,23 @@ program ]); material = result.material; } + // Ensure material is defined before proceeding + if (!material) { + console.error(chalk.red('No material selected. Exiting.')); + process.exit(1); + } console.log(material); // 3. Get the component dependencies by BFS (include depMaterials and depPackages) - const { allMaterials, allPackages } = bfsMaterials(material, materials); + const { allMaterials, allPackages } = bfsMaterials(material!, materials); // 4. Install the dependencies let flowgramPackage = `@flowgram.ai/editor`; if (projectInfo.flowgramVersion !== 'workspace:*') { flowgramPackage = `@flowgram.ai/editor@${projectInfo.flowgramVersion}`; } - const packagesToInstall = [flowgramPackage, ...allPackages]; + const packagesToInstall: string[] = [flowgramPackage, ...allPackages]; console.log(chalk.bold('These npm dependencies will be added to your project')); console.log(packagesToInstall); @@ -77,8 +85,9 @@ program // 5. Copy the materials to the project console.log(chalk.bold('These Materials will be added to your project')); console.log(allMaterials); - allMaterials.forEach((material) => { - copyMaterial(material, projectInfo); + allMaterials.forEach((mat: Material) => { + // Add type for mat + copyMaterial(mat, projectInfo); }); }); diff --git a/packages/materials/form-materials/bin/materials.js b/packages/materials/form-materials/bin/materials.js deleted file mode 100644 index 0123646b..00000000 --- a/packages/materials/form-materials/bin/materials.js +++ /dev/null @@ -1,93 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -const _types = ['components', 'effects', 'utils', 'typings']; - -export function listAllMaterials() { - const _materials = []; - - for (const _type of _types) { - const materialsPath = path.join(import.meta.dirname, '..', 'src', _type); - _materials.push( - ...fs - .readdirSync(materialsPath) - .map((_path) => { - if (_path === 'index.ts') { - return null; - } - - const config = fs.readFileSync(path.join(materialsPath, _path, 'config.json'), 'utf8'); - return { - ...JSON.parse(config), - type: _type, - path: path.join(materialsPath, _path), - }; - }) - .filter(Boolean) - ); - } - - return _materials; -} - -export function bfsMaterials(material, _materials = []) { - function findConfigByName(name) { - return _materials.find( - (_config) => _config.name === name || `${_config.type}/${_config.name}` === name - ); - } - - const queue = [material]; - const allMaterials = new Set(); - const allPackages = new Set(); - - while (queue.length > 0) { - const _material = queue.shift(); - if (allMaterials.has(_material)) { - continue; - } - allMaterials.add(_material); - - if (_material.depPackages) { - for (const _package of _material.depPackages) { - allPackages.add(_package); - } - } - - if (_material.depMaterials) { - for (const _materialName of _material.depMaterials) { - queue.push(findConfigByName(_materialName)); - } - } - } - - return { - allMaterials: Array.from(allMaterials), - allPackages: Array.from(allPackages), - }; -} - -export const copyMaterial = (material, projectInfo) => { - const sourceDir = material.path; - const materialRoot = path.join( - projectInfo.projectPath, - 'src', - 'form-materials', - `${material.type}` - ); - const targetDir = path.join(materialRoot, material.name); - fs.cpSync(sourceDir, targetDir, { recursive: true }); - - let materialRootIndexTs = ''; - if (fs.existsSync(path.join(materialRoot, 'index.ts'))) { - materialRootIndexTs = fs.readFileSync(path.join(materialRoot, 'index.ts'), 'utf8'); - } - if (!materialRootIndexTs.includes(material.name)) { - fs.writeFileSync( - path.join(materialRoot, 'index.ts'), - `${materialRootIndexTs}${materialRootIndexTs.endsWith('\n') ? '' : '\n'}export * from './${ - material.name - }';\n` - ); - } -}; diff --git a/packages/materials/form-materials/bin/materials.ts b/packages/materials/form-materials/bin/materials.ts new file mode 100644 index 00000000..6ace84e8 --- /dev/null +++ b/packages/materials/form-materials/bin/materials.ts @@ -0,0 +1,137 @@ +import { fileURLToPath } from 'url'; +import path from 'path'; +import fs from 'fs'; + +import { ProjectInfo } from './project'; // Import ProjectInfo + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Added type definitions +export interface Material { + name: string; + type: string; + path: string; + depPackages?: string[]; + depMaterials?: string[]; + [key: string]: any; // For other properties from config.json +} + +const _types: string[] = ['components', 'effects', 'utils', 'typings']; + +export function listAllMaterials(): Material[] { + const _materials: Material[] = []; + + for (const _type of _types) { + // 在 Node.js 中,import.meta.dirname 不可用,可使用 import.meta.url 结合 url 模块来获取目录路径 + + const materialsPath: string = path.join(__dirname, '..', 'src', _type); + _materials.push( + ...fs + .readdirSync(materialsPath) + .map((_path: string) => { + if (_path === 'index.ts') { + return null; + } + + const configPath = path.join(materialsPath, _path, 'config.json'); + // Check if config.json exists before reading + if (!fs.existsSync(configPath)) { + console.warn( + `Warning: config.json not found for material at ${path.join(materialsPath, _path)}` + ); + return null; + } + const configContent = fs.readFileSync(configPath, 'utf8'); + const config = JSON.parse(configContent); + + return { + ...config, + name: _path, // Assuming the folder name is the material name + type: _type, + path: path.join(materialsPath, _path), + } as Material; + }) + .filter((material): material is Material => material !== null) + ); + } + + return _materials; +} + +interface BfsResult { + allMaterials: Material[]; + allPackages: string[]; +} + +export function bfsMaterials(material: Material, _materials: Material[] = []): BfsResult { + function findConfigByName(name: string): Material | undefined { + return _materials.find( + (_config) => _config.name === name || `${_config.type}/${_config.name}` === name + ); + } + + const queue: (Material | undefined)[] = [material]; // Queue can hold undefined if findConfigByName returns undefined + const allMaterials = new Set(); + const allPackages = new Set(); + + while (queue.length > 0) { + const _material = queue.shift(); + if (!_material || allMaterials.has(_material)) { + // Check if _material is defined + continue; + } + allMaterials.add(_material); + + if (_material.depPackages) { + for (const _package of _material.depPackages) { + allPackages.add(_package); + } + } + + if (_material.depMaterials) { + for (const _materialName of _material.depMaterials) { + const depMaterial = findConfigByName(_materialName); + if (depMaterial) { + // Ensure dependent material is found before adding to queue + queue.push(depMaterial); + } else { + console.warn( + `Warning: Dependent material "${_materialName}" not found for material "${_material.name}".` + ); + } + } + } + } + + return { + allMaterials: Array.from(allMaterials), + allPackages: Array.from(allPackages), + }; +} + +export const copyMaterial = (material: Material, projectInfo: ProjectInfo): void => { + const sourceDir: string = material.path; + const materialRoot: string = path.join( + projectInfo.projectPath, + 'src', + 'form-materials', + `${material.type}` + ); + const targetDir = path.join(materialRoot, material.name); + fs.cpSync(sourceDir, targetDir, { recursive: true }); + + let materialRootIndexTs: string = ''; + const indexTsPath = path.join(materialRoot, 'index.ts'); + if (fs.existsSync(indexTsPath)) { + materialRootIndexTs = fs.readFileSync(indexTsPath, 'utf8'); + } + if (!materialRootIndexTs.includes(material.name)) { + fs.writeFileSync( + indexTsPath, + `${materialRootIndexTs}${materialRootIndexTs.endsWith('\n') ? '' : '\n'}export * from './${ + material.name + }';\n` + ); + } +}; diff --git a/packages/materials/form-materials/bin/project.js b/packages/materials/form-materials/bin/project.ts similarity index 64% rename from packages/materials/form-materials/bin/project.js rename to packages/materials/form-materials/bin/project.ts index 0530580f..c9eccbb1 100644 --- a/packages/materials/form-materials/bin/project.js +++ b/packages/materials/form-materials/bin/project.ts @@ -1,10 +1,25 @@ -import { execSync } from 'child_process'; -import fs from 'fs'; import path from 'path'; +import fs from 'fs'; +import { execSync } from 'child_process'; -export function getProjectInfo() { +// Added type definitions +interface PackageJson { + dependencies: { [key: string]: string }; + devDependencies?: { [key: string]: string }; + peerDependencies?: { [key: string]: string }; + [key: string]: any; +} + +export interface ProjectInfo { + projectPath: string; + packageJsonPath: string; + packageJson: PackageJson; + flowgramVersion: string; +} + +export function getProjectInfo(): ProjectInfo { // get nearest package.json - let projectPath = process.cwd(); + let projectPath: string = process.cwd(); while (projectPath !== '/' && !fs.existsSync(path.join(projectPath, 'package.json'))) { projectPath = path.join(projectPath, '..'); @@ -14,12 +29,11 @@ export function getProjectInfo() { throw new Error('Please run this command in a valid project'); } - const packageJsonPath = path.join(projectPath, 'package.json'); - - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + const packageJsonPath: string = path.join(projectPath, 'package.json'); + const packageJson: PackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // fixed layout or free layout - const flowgramVersion = + const flowgramVersion: string | undefined = packageJson.dependencies['@flowgram.ai/fixed-layout-editor'] || packageJson.dependencies['@flowgram.ai/free-layout-editor'] || packageJson.dependencies['@flowgram.ai/editor']; @@ -34,12 +48,12 @@ export function getProjectInfo() { projectPath, packageJsonPath, packageJson, - flowgramVersion, + flowgramVersion, // TypeScript will ensure this is string due to the check above }; } -export function findRushJson(startPath) { - let currentPath = startPath; +export function findRushJson(startPath: string): string | null { + let currentPath: string = startPath; while (currentPath !== '/' && !fs.existsSync(path.join(currentPath, 'rush.json'))) { currentPath = path.join(currentPath, '..'); } @@ -49,7 +63,7 @@ export function findRushJson(startPath) { return null; } -export function installDependencies(packages, projectInfo) { +export function installDependencies(packages: string[], projectInfo: ProjectInfo): void { if (fs.existsSync(path.join(projectInfo.projectPath, 'yarn.lock'))) { execSync(`yarn add ${packages.join(' ')}`, { stdio: 'inherit' }); return; diff --git a/packages/materials/form-materials/package.json b/packages/materials/form-materials/package.json index a31da2ad..38284fbd 100644 --- a/packages/materials/form-materials/package.json +++ b/packages/materials/form-materials/package.json @@ -4,7 +4,6 @@ "homepage": "https://flowgram.ai/", "repository": "https://github.com/bytedance/flowgram.ai", "license": "MIT", - "type": "module", "exports": { "types": "./dist/index.d.ts", "import": "./dist/esm/index.js", @@ -14,7 +13,7 @@ "module": "./dist/esm/index.js", "types": "./dist/index.d.ts", "bin": { - "flowgram-form-materials": "./bin/index.js" + "flowgram-form-materials": "./bin/index.ts" }, "files": [ "dist", @@ -48,8 +47,10 @@ "@flowgram.ai/eslint-config": "workspace:*", "@flowgram.ai/ts-config": "workspace:*", "@types/lodash": "^4.14.137", + "@types/node": "^18", "@types/react": "^18", "@types/react-dom": "^18", + "@types/inquirer": "9.0.7", "@types/styled-components": "^5", "eslint": "^8.54.0", "react": "^18", diff --git a/packages/materials/form-materials/tsconfig.json b/packages/materials/form-materials/tsconfig.json index 5c9c28f5..a189f267 100644 --- a/packages/materials/form-materials/tsconfig.json +++ b/packages/materials/form-materials/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json", "compilerOptions": { - "jsx": "react", + "jsx": "react" }, - "include": ["./src"], + "include": ["./src", "./bin/**/*.ts"], "exclude": ["node_modules"] }