Explorar o código

feat(material): use tsx in cli (#295)

Yiwei Mao hai 7 meses
pai
achega
b477181502

+ 6 - 0
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

+ 24 - 15
packages/materials/form-materials/bin/index.js → 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);
     });
   });
 

+ 0 - 93
packages/materials/form-materials/bin/materials.js

@@ -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`
-    );
-  }
-};

+ 137 - 0
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<Material>();
+  const allPackages = new Set<string>();
+
+  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`
+    );
+  }
+};

+ 26 - 12
packages/materials/form-materials/bin/project.js → 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;

+ 3 - 2
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",

+ 2 - 2
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"]
 }