@flowgram.ai/cli 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowgram.ai/cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "A CLI tool to create demo projects or sync materials",
5
5
  "bin": {
6
6
  "flowgram-cli": "./bin/index.js"
@@ -15,20 +15,20 @@
15
15
  "fs-extra": "^9.1.0",
16
16
  "commander": "^11.0.0",
17
17
  "chalk": "^5.3.0",
18
- "download": "8.0.0",
19
18
  "tar": "7.4.3",
20
- "inquirer": "^9.2.7",
19
+ "inquirer": "^12.9.4",
21
20
  "ignore": "~7.0.5"
22
21
  },
23
22
  "devDependencies": {
24
23
  "@types/download": "8.0.5",
25
24
  "@types/fs-extra": "11.0.4",
26
25
  "@types/node": "^18",
27
- "@types/inquirer": "9.0.7",
26
+ "@types/inquirer": "^9.0.9",
28
27
  "tsup": "^8.0.1",
29
28
  "eslint": "^8.54.0",
30
29
  "@typescript-eslint/parser": "^6.10.0",
31
- "typescript": "^5.8.3"
30
+ "typescript": "^5.8.3",
31
+ "@flowgram.ai/eslint-config": "0.4.13"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public",
@@ -36,6 +36,9 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist",
39
- "start": "node bin/index.js"
39
+ "watch": "tsup src/index.ts --format esm,cjs --out-dir dist --watch",
40
+ "start": "node bin/index.js",
41
+ "lint": "eslint ./src --cache",
42
+ "lint:fix": "eslint ./src --fix"
40
43
  }
41
44
  }
@@ -4,29 +4,58 @@
4
4
  */
5
5
 
6
6
  import path from 'path';
7
-
7
+ import https from 'https';
8
8
  import { execSync } from 'child_process';
9
+
10
+ import * as tar from 'tar';
9
11
  import inquirer from 'inquirer';
10
12
  import fs from 'fs-extra';
11
13
  import chalk from 'chalk';
12
- import download from 'download';
13
- import * as tar from 'tar';
14
-
15
- const args = process.argv.slice(2);
16
14
 
17
15
  const updateFlowGramVersions = (dependencies: any[], latestVersion: string) => {
18
- for(const packageName in dependencies) {
16
+ for (const packageName in dependencies) {
19
17
  if (packageName.startsWith('@flowgram.ai')) {
20
- dependencies[packageName] = latestVersion
18
+ dependencies[packageName] = latestVersion;
21
19
  }
22
20
  }
21
+ };
22
+
23
+ // 使用 https 下载文件
24
+ function downloadFile(url: string, dest: string): Promise<void> {
25
+ return new Promise((resolve, reject) => {
26
+ const file = fs.createWriteStream(dest);
27
+
28
+ https
29
+ .get(url, (response) => {
30
+ if (response.statusCode !== 200) {
31
+ reject(new Error(`Download failed: ${response.statusCode}`));
32
+ return;
33
+ }
34
+
35
+ response.pipe(file);
36
+
37
+ file.on('finish', () => {
38
+ file.close();
39
+ resolve();
40
+ });
41
+ })
42
+ .on('error', (err) => {
43
+ fs.unlink(dest, () => reject(err));
44
+ });
45
+
46
+ file.on('error', (err) => {
47
+ fs.unlink(dest, () => reject(err));
48
+ });
49
+ });
23
50
  }
24
51
 
25
52
  export const createApp = async (projectName?: string) => {
26
53
  console.log(chalk.green('Welcome to @flowgram.ai/create-app CLI!'));
27
- const latest = execSync('npm view @flowgram.ai/demo-fixed-layout version --tag=latest latest').toString().trim();
54
+ const latest = execSync('npm view @flowgram.ai/demo-fixed-layout version --tag=latest latest')
55
+ .toString()
56
+ .trim();
28
57
 
29
- let folderName = ''
58
+ let folderName = '';
30
59
 
31
60
  if (!projectName) {
32
61
  // 询问用户选择 demo 项目
@@ -42,14 +71,23 @@ export const createApp = async (projectName?: string) => {
42
71
  { name: 'Free Layout Demo Simple', value: 'demo-free-layout-simple' },
43
72
  { name: 'Free Layout Nextjs Demo', value: 'demo-nextjs' },
44
73
  { name: 'Free Layout Vite Demo Simple', value: 'demo-vite' },
45
- { name: 'Demo Playground for infinite canvas', value: 'demo-playground' }
74
+ { name: 'Demo Playground for infinite canvas', value: 'demo-playground' },
46
75
  ],
47
76
  },
48
77
  ]);
49
78
 
50
79
  folderName = repo;
51
80
  } else {
52
- if (['fixed-layout', 'free-layout', 'fixed-layout-simple', 'free-layout-simple', 'playground', 'nextjs'].includes(projectName)) {
81
+ if (
82
+ [
83
+ 'fixed-layout',
84
+ 'free-layout',
85
+ 'fixed-layout-simple',
86
+ 'free-layout-simple',
87
+ 'playground',
88
+ 'nextjs',
89
+ ].includes(projectName)
90
+ ) {
53
91
  folderName = `demo-${projectName}`;
54
92
  } else {
55
93
  console.error('Invalid projectName. Please run "npx create-app" to choose demo.');
@@ -59,45 +97,42 @@ export const createApp = async (projectName?: string) => {
59
97
 
60
98
  try {
61
99
  const targetDir = path.join(process.cwd());
100
+
62
101
  // 下载 npm 包的 tarball
63
102
  const downloadPackage = async () => {
64
103
  try {
65
- // npm registry 下载 tarball 文件
66
- const tarballBuffer = await download(`https://registry.npmjs.org/@flowgram.ai/${folderName}/-/${folderName}-${latest}.tgz`);
67
-
68
- // 确保目标文件夹存在
69
- fs.ensureDirSync(targetDir);
70
-
71
- // 创建一个临时文件名来保存 tarball 数据
104
+ const url = `https://registry.npmjs.org/@flowgram.ai/${folderName}/-/${folderName}-${latest}.tgz`;
72
105
  const tempTarballPath = path.join(process.cwd(), `${folderName}.tgz`);
73
106
 
74
- // 将下载的 tarball 写入临时文件
75
- fs.writeFileSync(tempTarballPath, tarballBuffer);
107
+ console.log(chalk.blue(`Downloading ${url} ...`));
108
+ await downloadFile(url, tempTarballPath);
109
+
110
+ fs.ensureDirSync(targetDir);
76
111
 
77
- // 解压 tarball 文件到目标文件夹
78
112
  await tar.x({
79
113
  file: tempTarballPath,
80
114
  C: targetDir,
81
115
  });
82
116
 
83
- fs.renameSync(path.join(targetDir, 'package'), path.join(targetDir, folderName))
117
+ fs.renameSync(path.join(targetDir, 'package'), path.join(targetDir, folderName));
84
118
 
85
- // 删除下载的 tarball 文件
86
119
  fs.unlinkSync(tempTarballPath);
87
120
  return true;
88
-
89
121
  } catch (error) {
90
122
  console.error(`Error downloading or extracting package: ${error}`);
91
123
  return false;
92
124
  }
93
125
  };
126
+
94
127
  const res = await downloadPackage();
95
128
 
96
- // 下载完成后,执行操作,替换 package.json 文件内部的所有 @flowgram.ai 包版本为 latest
129
+ // 替换 package.json 内部的 @flowgram.ai 包版本为 latest
97
130
  const pkgJsonPath = path.join(targetDir, folderName, 'package.json');
98
131
  const data = fs.readFileSync(pkgJsonPath, 'utf-8');
99
132
 
100
- const packageLatestVersion = execSync('npm view @flowgram.ai/core version --tag=latest latest').toString().trim();
133
+ const packageLatestVersion = execSync('npm view @flowgram.ai/core version --tag=latest latest')
134
+ .toString()
135
+ .trim();
101
136
 
102
137
  const jsonData = JSON.parse(data);
103
138
  if (jsonData.dependencies) {
@@ -108,23 +143,19 @@ export const createApp = async (projectName?: string) => {
108
143
  updateFlowGramVersions(jsonData.devDependencies, packageLatestVersion);
109
144
  }
110
145
 
111
- // 修改完成后写入
112
146
  fs.writeFileSync(pkgJsonPath, JSON.stringify(jsonData, null, 2), 'utf-8');
113
147
 
114
148
  if (res) {
115
- // 克隆项目
116
149
  console.log(chalk.green(`${folderName} Demo project created successfully!`));
117
-
118
150
  console.log(chalk.yellow('Run the following commands to start:'));
119
151
  console.log(chalk.cyan(` cd ${folderName}`));
120
152
  console.log(chalk.cyan(' npm install'));
121
153
  console.log(chalk.cyan(' npm start'));
122
154
  } else {
123
- console.log(chalk.red('Download failed'))
155
+ console.log(chalk.red('Download failed'));
124
156
  }
125
-
126
157
  } catch (error) {
127
158
  console.error('Error downloading repo:', error);
128
159
  return;
129
160
  }
130
- }
161
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+
8
+ import { traverseRecursiveTsFiles } from '../utils/ts-file';
9
+ import { Project } from '../utils/project';
10
+ import { loadNpm } from '../utils/npm';
11
+ import { Material } from '../materials/material';
12
+
13
+ export async function findUsedMaterials() {
14
+ // materialName can be undefined
15
+ console.log(chalk.bold('🚀 Welcome to @flowgram.ai form-materials CLI!'));
16
+
17
+ const project = await Project.getSingleton();
18
+ project.printInfo();
19
+
20
+ const formMaterialPkg = await loadNpm('@flowgram.ai/form-materials');
21
+ const materials: Material[] = Material.listAll(formMaterialPkg);
22
+
23
+ const allUsedMaterials = new Set<Material>();
24
+
25
+ const exportName2Material = new Map<string, Material>();
26
+
27
+ for (const material of materials) {
28
+ if (!material.indexFile) {
29
+ console.warn(`Material ${material.name} not found`);
30
+ return;
31
+ }
32
+
33
+ console.log(`👀 The exports of ${material.name} is ${material.allExportNames.join(',')}`);
34
+
35
+ material.allExportNames.forEach((exportName) => {
36
+ exportName2Material.set(exportName, material);
37
+ });
38
+ }
39
+
40
+ for (const tsFile of traverseRecursiveTsFiles(project.srcPath)) {
41
+ const fileMaterials = new Set<Material>();
42
+
43
+ let fileImportPrinted = false;
44
+ for (const importDeclaration of tsFile.imports) {
45
+ if (
46
+ !importDeclaration.source.startsWith('@flowgram.ai/form-materials') ||
47
+ !importDeclaration.namedImports?.length
48
+ ) {
49
+ continue;
50
+ }
51
+
52
+ if (!fileImportPrinted) {
53
+ fileImportPrinted = true;
54
+ console.log(chalk.bold(`\n👀 Searching ${tsFile.path}`));
55
+ }
56
+
57
+ console.log(`🔍 ${importDeclaration.statement}`);
58
+
59
+ if (importDeclaration.namedImports) {
60
+ importDeclaration.namedImports.forEach((namedImport) => {
61
+ const material = exportName2Material.get(namedImport.imported);
62
+
63
+ if (material) {
64
+ fileMaterials.add(material);
65
+ allUsedMaterials.add(material);
66
+ console.log(`import ${chalk.bold(material.fullName)} by ${namedImport.imported}`);
67
+ }
68
+ });
69
+ }
70
+ }
71
+ }
72
+
73
+ console.log(chalk.bold('\n📦 All used materials:'));
74
+ console.log([...allUsedMaterials].map((_material) => _material.fullName).join(','));
75
+ }
package/src/index.ts CHANGED
@@ -3,44 +3,59 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { Command } from "commander";
6
+ import path from 'path';
7
7
 
8
- import { syncMaterial } from "./materials";
9
- import { createApp } from "./create-app";
10
- import { updateFlowgramVersion } from "./update-version";
8
+ import { Command } from 'commander';
9
+
10
+ import { updateFlowgramVersion } from './update-version';
11
+ import { syncMaterial } from './materials';
12
+ import { findUsedMaterials } from './find-materials';
13
+ import { createApp } from './create-app';
11
14
 
12
15
  const program = new Command();
13
16
 
14
- program.name("flowgram-cli").version("1.0.0").description("Flowgram CLI");
17
+ program.name('flowgram-cli').version('1.0.0').description('Flowgram CLI');
15
18
 
16
19
  program
17
- .command("create-app")
18
- .description("Create a new flowgram project")
19
- .argument("[string]", "Project name")
20
+ .command('create-app')
21
+ .description('Create a new flowgram project')
22
+ .argument('[string]', 'Project name')
20
23
  .action(async (projectName) => {
21
24
  await createApp(projectName);
22
25
  });
23
26
 
24
27
  program
25
- .command("materials")
26
- .description("Sync materials to the project")
27
- .argument("[string]", "Material name")
28
- .option(
29
- "--refresh-project-imports",
30
- "Refresh project imports to copied materials",
31
- false,
28
+ .command('materials')
29
+ .description('Sync materials to the project')
30
+ .argument(
31
+ '[string]',
32
+ 'Material name or names\nExample 1: components/variable-selector \nExample2: components/variable-selector,effect/provideJsonSchemaOutputs'
32
33
  )
34
+ .option('--refresh-project-imports', 'Refresh project imports to copied materials', false)
35
+ .option('--target-material-root-dir <string>', 'Target directory to copy materials')
36
+ .option('--select-multiple', 'Select multiple materials', false)
33
37
  .action(async (materialName, options) => {
34
38
  await syncMaterial({
35
39
  materialName,
36
40
  refreshProjectImports: options.refreshProjectImports,
41
+ targetMaterialRootDir: options.targetMaterialRootDir
42
+ ? path.join(process.cwd(), options.targetMaterialRootDir)
43
+ : undefined,
44
+ selectMultiple: options.selectMultiple,
37
45
  });
38
46
  });
39
47
 
40
48
  program
41
- .command("update-version")
42
- .description("Update flowgram version in the project")
43
- .argument("[string]", "Flowgram version")
49
+ .command('find-used-materials')
50
+ .description('Find used materials in the project')
51
+ .action(async () => {
52
+ await findUsedMaterials();
53
+ });
54
+
55
+ program
56
+ .command('update-version')
57
+ .description('Update flowgram version in the project')
58
+ .argument('[string]', 'Flowgram version')
44
59
  .action(async (version) => {
45
60
  await updateFlowgramVersion(version);
46
61
  });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+
9
+ import { traverseRecursiveTsFiles } from '../utils/ts-file';
10
+ import { SyncMaterialContext } from './types';
11
+
12
+ interface CopyMaterialReturn {
13
+ packagesToInstall: string[];
14
+ }
15
+
16
+ export const copyMaterials = (ctx: SyncMaterialContext): CopyMaterialReturn => {
17
+ const { selectedMaterials, project, formMaterialPkg, targetFormMaterialRoot } = ctx;
18
+ const formMaterialDependencies = formMaterialPkg.dependencies;
19
+ const packagesToInstall: Set<string> = new Set();
20
+
21
+ for (const material of selectedMaterials) {
22
+ const sourceDir: string = material.sourceDir;
23
+ const targetDir: string = path.join(targetFormMaterialRoot, material.type, material.name);
24
+
25
+ fs.cpSync(sourceDir, targetDir, { recursive: true });
26
+
27
+ for (const file of traverseRecursiveTsFiles(targetDir)) {
28
+ for (const importDeclaration of file.imports) {
29
+ const { source } = importDeclaration;
30
+
31
+ if (source.startsWith('@/')) {
32
+ // is inner import
33
+ console.log(`Replace Import from ${source} to @flowgram.ai/form-materials`);
34
+ file.replaceImport(
35
+ [importDeclaration],
36
+ [{ ...importDeclaration, source: '@flowgram.ai/form-materials' }]
37
+ );
38
+ packagesToInstall.add(`@flowgram.ai/form-materials@${project.flowgramVersion}`);
39
+ } else if (!source.startsWith('.') && !source.startsWith('react')) {
40
+ // check if is in form material dependencies
41
+ const [dep, version] =
42
+ Object.entries(formMaterialDependencies).find(([_key]) => source.startsWith(_key)) ||
43
+ [];
44
+ if (!dep) {
45
+ continue;
46
+ }
47
+ if (dep.startsWith('@flowgram.ai/')) {
48
+ packagesToInstall.add(`${dep}@${project.flowgramVersion}`);
49
+ } else {
50
+ packagesToInstall.add(`${dep}@${version}`);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ return {
58
+ packagesToInstall: [...packagesToInstall],
59
+ };
60
+ };
@@ -3,105 +3,76 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import inquirer from "inquirer";
7
- import chalk from "chalk";
6
+ import path from 'path';
8
7
 
9
- import { copyMaterial, listAllMaterials, Material } from "./materials";
10
- import { loadNpm } from "../utils/npm";
11
- import path from "path";
12
- import { Project } from "../utils/project";
13
- import { executeRefreshProjectImport } from "./refresh-project-import";
8
+ import chalk from 'chalk';
14
9
 
15
- export async function syncMaterial(opts: {
16
- materialName?: string;
17
- refreshProjectImports?: boolean;
18
- }) {
19
- const { materialName, refreshProjectImports } = opts;
10
+ import { Project } from '../utils/project';
11
+ import { loadNpm } from '../utils/npm';
12
+ import { MaterialCliOptions, SyncMaterialContext } from './types';
13
+ import { getSelectedMaterials } from './select';
14
+ import { executeRefreshProjectImport } from './refresh-project-import';
15
+ import { Material } from './material';
16
+ import { copyMaterials } from './copy';
17
+
18
+ export async function syncMaterial(cliOpts: MaterialCliOptions) {
19
+ const { refreshProjectImports, targetMaterialRootDir } = cliOpts;
20
20
 
21
21
  // materialName can be undefined
22
- console.log(chalk.bold("🚀 Welcome to @flowgram.ai form-materials!"));
22
+ console.log(chalk.bold('🚀 Welcome to @flowgram.ai form-materials CLI!'));
23
23
 
24
24
  const project = await Project.getSingleton();
25
25
  project.printInfo();
26
26
 
27
+ // where to place all material in target project
28
+ const targetFormMaterialRoot =
29
+ targetMaterialRootDir || path.join(project.projectPath, 'src', 'form-materials');
30
+ console.log(chalk.black(` - Target material root: ${targetFormMaterialRoot}`));
31
+
27
32
  if (!project.flowgramVersion) {
28
33
  throw new Error(
29
34
  chalk.red(
30
- "❌ Please install @flowgram.ai/fixed-layout-editor or @flowgram.ai/free-layout-editor",
31
- ),
35
+ '❌ Please install @flowgram.ai/fixed-layout-editor or @flowgram.ai/free-layout-editor'
36
+ )
32
37
  );
33
38
  }
34
39
 
35
- const formMaterialPath = await loadNpm("@flowgram.ai/form-materials");
36
- const formMaterialSrc = path.join(formMaterialPath, "src");
37
-
38
- const materials: Material[] = listAllMaterials(formMaterialSrc);
40
+ const formMaterialPkg = await loadNpm('@flowgram.ai/form-materials');
39
41
 
40
- let material: Material | undefined; // material can be undefined
41
-
42
- // 1. Check if materialName is provided and exists in materials
43
- if (materialName) {
44
- const selectedMaterial = materials.find(
45
- (m) => `${m.type}/${m.name}` === materialName,
46
- );
47
- if (selectedMaterial) {
48
- material = selectedMaterial;
49
- console.log(chalk.green(`Using material: ${materialName}`));
50
- } else {
51
- console.log(
52
- chalk.yellow(
53
- `Material "${materialName}" not found. Please select from the list:`,
54
- ),
55
- );
56
- }
57
- }
42
+ let selectedMaterials: Material[] = await getSelectedMaterials(cliOpts, formMaterialPkg);
58
43
 
59
- // 2. If material not found or materialName not provided, prompt user to select
60
- if (!material) {
61
- // User select one component
62
- const result = await inquirer.prompt<{
63
- material: Material; // Specify type for prompt result
64
- }>([
65
- {
66
- type: "list",
67
- name: "material",
68
- message: "Select one material to add:",
69
- choices: [
70
- ...materials.map((_material) => ({
71
- name: `${_material.type}/${_material.name}`,
72
- value: _material,
73
- })),
74
- ],
75
- },
76
- ]);
77
- material = result.material;
78
- }
79
44
  // Ensure material is defined before proceeding
80
- if (!material) {
81
- console.error(chalk.red("No material selected. Exiting."));
45
+ if (!selectedMaterials.length) {
46
+ console.error(chalk.red('No material selected. Exiting.'));
82
47
  process.exit(1);
83
48
  }
84
49
 
85
- // 3. Refresh project imports
50
+ const context: SyncMaterialContext = {
51
+ selectedMaterials: selectedMaterials,
52
+ project,
53
+ formMaterialPkg,
54
+ cliOpts,
55
+ targetFormMaterialRoot,
56
+ };
57
+
58
+ // Copy the materials to the project
59
+ console.log(chalk.bold('🚀 The following materials will be added to your project'));
60
+ console.log(selectedMaterials.map((material) => `📦 ${material.fullName}`).join('\n'));
61
+ console.log('\n');
62
+
63
+ let { packagesToInstall } = copyMaterials(context);
64
+
65
+ // Refresh project imports
86
66
  if (refreshProjectImports) {
87
- console.log(chalk.bold("🚀 Refresh imports in your project"));
88
- executeRefreshProjectImport(project, material);
67
+ console.log(chalk.bold('🚀 Refresh imports in your project'));
68
+ executeRefreshProjectImport(context);
89
69
  }
90
70
 
91
- // 4. Copy the materials to the project
92
- console.log(
93
- chalk.bold("🚀 The following materials will be added to your project"),
94
- );
95
- console.log(material);
96
- let { packagesToInstall } = copyMaterial(material, project, formMaterialPath);
97
-
98
- // 4. Install the dependencies
71
+ // Install the dependencies
99
72
  await project.addDependencies(packagesToInstall);
100
- console.log(
101
- chalk.bold("✅ These npm dependencies is added to your package.json"),
102
- );
73
+ console.log(chalk.bold('\n✅ These npm dependencies is added to your package.json'));
103
74
  packagesToInstall.forEach((_package) => {
104
75
  console.log(`- ${_package}`);
105
76
  });
106
- console.log(chalk.bold("\n➡️ Please run npm install to install dependencies\n"));
77
+ console.log(chalk.bold(chalk.bold('\n➡️ Please run npm install to install dependencies\n')));
107
78
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import path from 'path';
7
+ import { readdirSync } from 'fs';
8
+
9
+ import { getIndexTsFile } from '../utils/ts-file';
10
+ import { LoadedNpmPkg } from '../utils/npm';
11
+
12
+ export class Material {
13
+ protected static _all_materials_cache: Material[] = [];
14
+
15
+ static ALL_TYPES = [
16
+ 'components',
17
+ 'effects',
18
+ 'plugins',
19
+ 'shared',
20
+ 'validate',
21
+ 'form-plugins',
22
+ 'hooks',
23
+ ];
24
+
25
+ constructor(public type: string, public name: string, public formMaterialPkg: LoadedNpmPkg) {}
26
+
27
+ get fullName() {
28
+ return `${this.type}/${this.name}`;
29
+ }
30
+
31
+ get sourceDir() {
32
+ return path.join(this.formMaterialPkg.srcPath, this.type, this.name);
33
+ }
34
+
35
+ get indexFile() {
36
+ return getIndexTsFile(this.sourceDir);
37
+ }
38
+
39
+ get allExportNames() {
40
+ return this.indexFile?.allExportNames || [];
41
+ }
42
+
43
+ static listAll(formMaterialPkg: LoadedNpmPkg): Material[] {
44
+ if (!this._all_materials_cache.length) {
45
+ this._all_materials_cache = Material.ALL_TYPES.map((type) => {
46
+ const materialsPath: string = path.join(formMaterialPkg.srcPath, type);
47
+ return readdirSync(materialsPath)
48
+ .map((_path: string) => {
49
+ if (_path === 'index.ts') {
50
+ return null;
51
+ }
52
+
53
+ return new Material(type, _path, formMaterialPkg);
54
+ })
55
+ .filter((material): material is Material => material !== null);
56
+ }).flat();
57
+ }
58
+
59
+ return this._all_materials_cache;
60
+ }
61
+ }