@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/dist/index.cjs +576 -428
- package/dist/index.js +576 -428
- package/package.json +9 -6
- package/src/create-app/index.ts +63 -32
- package/src/find-materials/index.ts +75 -0
- package/src/index.ts +33 -18
- package/src/materials/copy.ts +60 -0
- package/src/materials/index.ts +45 -74
- package/src/materials/material.ts +61 -0
- package/src/materials/refresh-project-import.ts +55 -36
- package/src/materials/select.ts +69 -0
- package/src/materials/types.ts +23 -0
- package/src/update-version/index.ts +12 -11
- package/src/utils/export.ts +8 -11
- package/src/utils/file.ts +14 -13
- package/src/utils/import.ts +7 -5
- package/src/utils/npm.ts +92 -19
- package/src/utils/project.ts +20 -27
- package/src/utils/ts-file.ts +23 -38
- package/src/materials/materials.ts +0 -127
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowgram.ai/cli",
|
|
3
|
-
"version": "0.4.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
}
|
package/src/create-app/index.ts
CHANGED
|
@@ -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')
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
//
|
|
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')
|
|
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
|
|
6
|
+
import path from 'path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import { updateFlowgramVersion } from
|
|
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(
|
|
17
|
+
program.name('flowgram-cli').version('1.0.0').description('Flowgram CLI');
|
|
15
18
|
|
|
16
19
|
program
|
|
17
|
-
.command(
|
|
18
|
-
.description(
|
|
19
|
-
.argument(
|
|
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(
|
|
26
|
-
.description(
|
|
27
|
-
.argument(
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
42
|
-
.description(
|
|
43
|
-
.
|
|
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
|
+
};
|
package/src/materials/index.ts
CHANGED
|
@@ -3,105 +3,76 @@
|
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import chalk from "chalk";
|
|
6
|
+
import path from 'path';
|
|
8
7
|
|
|
9
|
-
import
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
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(
|
|
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
|
-
|
|
31
|
-
)
|
|
35
|
+
'❌ Please install @flowgram.ai/fixed-layout-editor or @flowgram.ai/free-layout-editor'
|
|
36
|
+
)
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
const
|
|
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
|
|
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 (!
|
|
81
|
-
console.error(chalk.red(
|
|
45
|
+
if (!selectedMaterials.length) {
|
|
46
|
+
console.error(chalk.red('No material selected. Exiting.'));
|
|
82
47
|
process.exit(1);
|
|
83
48
|
}
|
|
84
49
|
|
|
85
|
-
|
|
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(
|
|
88
|
-
executeRefreshProjectImport(
|
|
67
|
+
console.log(chalk.bold('🚀 Refresh imports in your project'));
|
|
68
|
+
executeRefreshProjectImport(context);
|
|
89
69
|
}
|
|
90
70
|
|
|
91
|
-
//
|
|
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(
|
|
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
|
+
}
|