@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.
@@ -3,54 +3,73 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import chalk from "chalk";
7
- import { ImportDeclaration } from "../utils/import";
8
- import { Project } from "../utils/project";
9
- import { getIndexTsFile, traverseRecursiveTsFiles } from "../utils/ts-file";
10
- import { Material } from "./materials";
11
-
12
- export function executeRefreshProjectImport(
13
- project: Project,
14
- material: Material,
15
- ) {
16
- const materialFile = getIndexTsFile(material.path);
17
-
18
- if (!materialFile) {
19
- console.warn(`Material ${material.name} not found`);
20
- return;
21
- }
6
+ import path from 'path';
7
+
8
+ import chalk from 'chalk';
9
+
10
+ import { traverseRecursiveTsFiles } from '../utils/ts-file';
11
+ import { ImportDeclaration, NamedImport } from '../utils/import';
12
+ import { SyncMaterialContext } from './types';
13
+ import { Material } from './material';
22
14
 
23
- const targetDir = `@/form-materials/${material.type}/${material.name}`;
15
+ export function executeRefreshProjectImport(context: SyncMaterialContext) {
16
+ const { selectedMaterials, project, targetFormMaterialRoot } = context;
24
17
 
25
- const exportNames = materialFile.allExportNames;
18
+ const exportName2Material = new Map<string, Material>();
26
19
 
27
- console.log(`👀 The exports of ${material.name} is ${exportNames.join(",")}`);
20
+ const targetModule = `@/${path.relative(project.srcPath, targetFormMaterialRoot)}`;
21
+
22
+ for (const material of selectedMaterials) {
23
+ if (!material.indexFile) {
24
+ console.warn(`Material ${material.name} not found`);
25
+ return;
26
+ }
27
+
28
+ console.log(`👀 The exports of ${material.name} is ${material.allExportNames.join(',')}`);
29
+
30
+ material.allExportNames.forEach((exportName) => {
31
+ exportName2Material.set(exportName, material);
32
+ });
33
+ }
28
34
 
29
35
  for (const tsFile of traverseRecursiveTsFiles(project.srcPath)) {
30
36
  for (const importDeclaration of tsFile.imports) {
31
- if (importDeclaration.source === "@flowgram.ai/form-materials") {
32
- const currentMaterialImports = importDeclaration.namedImports?.filter(
33
- (item) => exportNames.includes(item.imported),
34
- );
35
- if (!currentMaterialImports?.length) {
37
+ if (importDeclaration.source.startsWith('@flowgram.ai/form-materials')) {
38
+ // Import Module and its related named Imported
39
+ const restImports: NamedImport[] = [];
40
+ const importMap: Record<string, NamedImport[]> = {};
41
+
42
+ if (!importDeclaration.namedImports) {
36
43
  continue;
37
44
  }
38
- const nextImports: ImportDeclaration[] = [
39
- {
40
- ...importDeclaration,
41
- namedImports: currentMaterialImports,
42
- source: targetDir,
43
- },
44
- ];
45
45
 
46
- const keepImportNames = importDeclaration.namedImports?.filter(
47
- (item) => !exportNames.includes(item.imported),
46
+ for (const nameImport of importDeclaration.namedImports) {
47
+ const material = exportName2Material.get(nameImport.imported);
48
+ if (material) {
49
+ const importModule = `${targetModule}/${material.fullName}`;
50
+ importMap[importModule] = importMap[importModule] || [];
51
+ importMap[importModule].push(nameImport);
52
+ } else {
53
+ restImports.push(nameImport);
54
+ }
55
+ }
56
+
57
+ if (Object.keys(importMap).length === 0) {
58
+ continue;
59
+ }
60
+
61
+ const nextImports: ImportDeclaration[] = Object.entries(importMap).map(
62
+ ([importModule, namedImports]) => ({
63
+ ...importDeclaration,
64
+ namedImports,
65
+ source: importModule,
66
+ })
48
67
  );
49
68
 
50
- if (keepImportNames?.length) {
69
+ if (restImports?.length) {
51
70
  nextImports.unshift({
52
71
  ...importDeclaration,
53
- namedImports: keepImportNames,
72
+ namedImports: restImports,
54
73
  });
55
74
  }
56
75
 
@@ -60,7 +79,7 @@ export function executeRefreshProjectImport(
60
79
  console.log(
61
80
  `From:\n${importDeclaration.statement}\nTo:\n${nextImports
62
81
  .map((item) => item.statement)
63
- .join("\n")}`,
82
+ .join('\n')}`
64
83
  );
65
84
  }
66
85
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import inquirer from 'inquirer';
7
+ import chalk from 'chalk';
8
+
9
+ import { LoadedNpmPkg } from '../utils/npm';
10
+ import { MaterialCliOptions } from './types';
11
+ import { Material } from './material';
12
+
13
+ export const getSelectedMaterials = async (
14
+ cliOpts: MaterialCliOptions,
15
+ formMaterialPkg: LoadedNpmPkg
16
+ ) => {
17
+ const { materialName, selectMultiple } = cliOpts;
18
+
19
+ const materials: Material[] = Material.listAll(formMaterialPkg);
20
+
21
+ let selectedMaterials: Material[] = [];
22
+
23
+ // 1. Check if materialName is provided and exists in materials
24
+ if (materialName) {
25
+ selectedMaterials = materialName
26
+ .split(',')
27
+ .map((_name) => materials.find((_m) => _m.fullName === _name.trim())!)
28
+ .filter(Boolean);
29
+ }
30
+
31
+ // 2. If material not found or materialName not provided, prompt user to select
32
+ if (!selectedMaterials.length) {
33
+ console.log(chalk.yellow(`Material "${materialName}" not found. Please select from the list:`));
34
+
35
+ const choices = materials.map((_material) => ({
36
+ name: _material.fullName,
37
+ value: _material,
38
+ }));
39
+ if (selectMultiple) {
40
+ // User select one component
41
+ const result = await inquirer.prompt<{
42
+ material: Material[]; // Specify type for prompt result
43
+ }>([
44
+ {
45
+ type: 'checkbox',
46
+ name: 'material',
47
+ message: 'Select multiple materials to add:',
48
+ choices: choices,
49
+ },
50
+ ]);
51
+ selectedMaterials = result.material;
52
+ } else {
53
+ // User select one component
54
+ const result = await inquirer.prompt<{
55
+ material: Material; // Specify type for prompt result
56
+ }>([
57
+ {
58
+ type: 'list',
59
+ name: 'material',
60
+ message: 'Select one material to add:',
61
+ choices: choices,
62
+ },
63
+ ]);
64
+ selectedMaterials = [result.material];
65
+ }
66
+ }
67
+
68
+ return selectedMaterials;
69
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Project } from '../utils/project';
7
+ import { LoadedNpmPkg } from '../utils/npm';
8
+ import { Material } from './material';
9
+
10
+ export interface MaterialCliOptions {
11
+ materialName?: string;
12
+ refreshProjectImports?: boolean;
13
+ targetMaterialRootDir?: string;
14
+ selectMultiple?: boolean;
15
+ }
16
+
17
+ export interface SyncMaterialContext {
18
+ selectedMaterials: Material[];
19
+ project: Project;
20
+ formMaterialPkg: LoadedNpmPkg;
21
+ cliOpts: MaterialCliOptions;
22
+ targetFormMaterialRoot: string;
23
+ }
@@ -3,30 +3,31 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { getLatestVersion } from "../utils/npm";
7
- import { traverseRecursiveFiles } from "../utils/file";
8
- import chalk from "chalk";
6
+ import chalk from 'chalk';
7
+
8
+ import { getLatestVersion } from '../utils/npm';
9
+ import { traverseRecursiveFiles } from '../utils/file';
9
10
 
10
11
  export async function updateFlowgramVersion(inputVersion?: string) {
11
- console.log(chalk.bold("🚀 Welcome to @flowgram.ai update-version helper"));
12
+ console.log(chalk.bold('🚀 Welcome to @flowgram.ai update-version helper'));
12
13
 
13
14
  // Get latest version
14
- const latestVersion = await getLatestVersion("@flowgram.ai/editor");
15
+ const latestVersion = await getLatestVersion('@flowgram.ai/editor');
15
16
  const currentPath = process.cwd();
16
- console.log("- Latest flowgram version: ", latestVersion);
17
- console.log("- Current Path: ", currentPath);
17
+ console.log('- Latest flowgram version: ', latestVersion);
18
+ console.log('- Current Path: ', currentPath);
18
19
 
19
20
  // User Input flowgram version, default is latestVersion
20
21
  const flowgramVersion: string = inputVersion || latestVersion;
21
22
 
22
23
  for (const file of traverseRecursiveFiles(currentPath)) {
23
- if (file.path.endsWith("package.json")) {
24
- console.log("👀 Find package.json: ", file.path);
24
+ if (file.path.endsWith('package.json')) {
25
+ console.log('👀 Find package.json: ', file.path);
25
26
  let updated = false;
26
27
  const json = JSON.parse(file.content);
27
28
  if (json.dependencies) {
28
29
  for (const key in json.dependencies) {
29
- if (key.startsWith("@flowgram.ai/")) {
30
+ if (key.startsWith('@flowgram.ai/')) {
30
31
  updated = true;
31
32
  json.dependencies[key] = flowgramVersion;
32
33
  console.log(`- Update ${key} to ${flowgramVersion}`);
@@ -35,7 +36,7 @@ export async function updateFlowgramVersion(inputVersion?: string) {
35
36
  }
36
37
  if (json.devDependencies) {
37
38
  for (const key in json.devDependencies) {
38
- if (key.startsWith("@flowgram.ai/")) {
39
+ if (key.startsWith('@flowgram.ai/')) {
39
40
  updated = true;
40
41
  json.devDependencies[key] = flowgramVersion;
41
42
  console.log(`- Update ${key} to ${flowgramVersion}`);
@@ -9,10 +9,7 @@ export function extractNamedExports(content: string) {
9
9
 
10
10
  // Collect all type definition names
11
11
  const typeDefinitions = new Set();
12
- const typePatterns = [
13
- /\b(?:type|interface)\s+(\w+)/g,
14
- /\bexport\s+(?:type|interface)\s+(\w+)/g,
15
- ];
12
+ const typePatterns = [/\b(?:type|interface)\s+(\w+)/g, /\bexport\s+(?:type|interface)\s+(\w+)/g];
16
13
 
17
14
  let match;
18
15
  for (const pattern of typePatterns) {
@@ -41,7 +38,7 @@ export function extractNamedExports(content: string) {
41
38
  exportPatterns[0].lastIndex = 0;
42
39
  while ((match = exportPatterns[0].exec(content)) !== null) {
43
40
  const [, kind, name] = match;
44
- if (kind === "type" || kind === "interface" || typeDefinitions.has(name)) {
41
+ if (kind === 'type' || kind === 'interface' || typeDefinitions.has(name)) {
45
42
  typeExports.push(name);
46
43
  } else {
47
44
  valueExports.push(name);
@@ -52,13 +49,13 @@ export function extractNamedExports(content: string) {
52
49
  exportPatterns[1].lastIndex = 0;
53
50
  while ((match = exportPatterns[1].exec(content)) !== null) {
54
51
  const exportsList = match[1]
55
- .split(",")
52
+ .split(',')
56
53
  .map((item) => item.trim())
57
- .filter((item) => item && !item.includes(" as "));
54
+ .filter((item) => item && !item.includes(' as '));
58
55
 
59
56
  for (const name of exportsList) {
60
- if (name.startsWith("type ")) {
61
- typeExports.push(name.replace("type ", "").trim());
57
+ if (name.startsWith('type ')) {
58
+ typeExports.push(name.replace('type ', '').trim());
62
59
  } else if (typeDefinitions.has(name)) {
63
60
  typeExports.push(name);
64
61
  } else {
@@ -93,9 +90,9 @@ export function extractNamedExports(content: string) {
93
90
  exportPatterns[4].lastIndex = 0;
94
91
  while ((match = exportPatterns[4].exec(content)) !== null) {
95
92
  const exportsList = match[1]
96
- .split(",")
93
+ .split(',')
97
94
  .map((item) => item.trim())
98
- .filter((item) => item && !item.includes(" as "));
95
+ .filter((item) => item && !item.includes(' as '));
99
96
 
100
97
  for (const name of exportsList) {
101
98
  typeExports.push(name);
package/src/utils/file.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import path from "path";
7
- import fs from "fs";
8
- import ignore, { Ignore } from "ignore";
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+
9
+ import ignore, { Ignore } from 'ignore';
9
10
 
10
11
  export class File {
11
12
  content: string;
@@ -18,7 +19,7 @@ export class File {
18
19
 
19
20
  suffix: string;
20
21
 
21
- constructor(filePath: string, public root: string = "/") {
22
+ constructor(filePath: string, public root: string = '/') {
22
23
  this.path = filePath;
23
24
  this.relativePath = path.relative(this.root, this.path);
24
25
  this.suffix = path.extname(this.path);
@@ -30,7 +31,7 @@ export class File {
30
31
 
31
32
  // If no utf-8, skip
32
33
  try {
33
- this.content = fs.readFileSync(this.path, "utf-8");
34
+ this.content = fs.readFileSync(this.path, 'utf-8');
34
35
  this.isUtf8 = true;
35
36
  } catch (e) {
36
37
  this.isUtf8 = false;
@@ -40,29 +41,29 @@ export class File {
40
41
 
41
42
  replace(updater: (content: string) => string) {
42
43
  if (!this.isUtf8) {
43
- console.warn("Not UTF-8 file skipped: ", this.path);
44
+ console.warn('Not UTF-8 file skipped: ', this.path);
44
45
  return;
45
46
  }
46
47
  this.content = updater(this.content);
47
- fs.writeFileSync(this.path, this.content, "utf-8");
48
+ fs.writeFileSync(this.path, this.content, 'utf-8');
48
49
  }
49
50
 
50
51
  write(nextContent: string) {
51
52
  this.content = nextContent;
52
- fs.writeFileSync(this.path, this.content, "utf-8");
53
+ fs.writeFileSync(this.path, this.content, 'utf-8');
53
54
  }
54
55
  }
55
56
 
56
57
  export function* traverseRecursiveFilePaths(
57
58
  folder: string,
58
- ig: Ignore = ignore().add(".git"),
59
- root: string = folder,
59
+ ig: Ignore = ignore().add('.git'),
60
+ root: string = folder
60
61
  ): Generator<string> {
61
62
  const files = fs.readdirSync(folder);
62
63
 
63
64
  // add .gitignore to ignore if exists
64
- if (fs.existsSync(path.join(folder, ".gitignore"))) {
65
- ig.add(fs.readFileSync(path.join(folder, ".gitignore"), "utf-8"));
65
+ if (fs.existsSync(path.join(folder, '.gitignore'))) {
66
+ ig.add(fs.readFileSync(path.join(folder, '.gitignore'), 'utf-8'));
66
67
  }
67
68
 
68
69
  for (const file of files) {
@@ -82,6 +83,6 @@ export function* traverseRecursiveFilePaths(
82
83
 
83
84
  export function* traverseRecursiveFiles(folder: string): Generator<File> {
84
85
  for (const filePath of traverseRecursiveFilePaths(folder)) {
85
- yield new File(filePath);
86
+ yield new File(filePath, folder);
86
87
  }
87
88
  }
@@ -3,6 +3,12 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
+ export interface NamedImport {
7
+ local?: string;
8
+ imported: string;
9
+ typeOnly?: boolean;
10
+ }
11
+
6
12
  /**
7
13
  * Cases
8
14
  * import { A, B } from 'module';
@@ -16,11 +22,7 @@ export interface ImportDeclaration {
16
22
  statement: string;
17
23
 
18
24
  // import { A, B } from 'module';
19
- namedImports?: {
20
- local?: string;
21
- imported: string;
22
- typeOnly?: boolean;
23
- }[];
25
+ namedImports?: NamedImport[];
24
26
 
25
27
  // import A from 'module';
26
28
  defaultImport?: string;
package/src/utils/npm.ts CHANGED
@@ -3,41 +3,114 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { execSync } from 'child_process';
7
- import { existsSync } from 'fs';
8
6
  import path from 'path';
9
- import download from 'download';
7
+ import https from 'https';
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import { execSync } from 'child_process';
10
+
11
+ import * as tar from 'tar';
12
+ import fs from 'fs-extra';
13
+
14
+ export class LoadedNpmPkg {
15
+ constructor(public name: string, public version: string, public path: string) {}
16
+
17
+ get srcPath() {
18
+ return path.join(this.path, 'src');
19
+ }
20
+
21
+ get distPath() {
22
+ return path.join(this.path, 'dist');
23
+ }
24
+
25
+ protected _packageJson: Record<string, any>;
26
+
27
+ get packageJson() {
28
+ if (!this._packageJson) {
29
+ this._packageJson = JSON.parse(readFileSync(path.join(this.path, 'package.json'), 'utf8'));
30
+ }
31
+ return this._packageJson;
32
+ }
33
+
34
+ get dependencies(): Record<string, string> {
35
+ return this.packageJson.dependencies;
36
+ }
37
+ }
38
+
39
+ // 使用 https 下载文件
40
+ function downloadFile(url: string, dest: string): Promise<void> {
41
+ return new Promise((resolve, reject) => {
42
+ const file = fs.createWriteStream(dest);
43
+
44
+ https
45
+ .get(url, (response) => {
46
+ if (response.statusCode !== 200) {
47
+ reject(new Error(`Download failed: ${response.statusCode}`));
48
+ return;
49
+ }
50
+
51
+ response.pipe(file);
52
+
53
+ file.on('finish', () => {
54
+ file.close();
55
+ resolve();
56
+ });
57
+ })
58
+ .on('error', (err) => {
59
+ fs.unlink(dest, () => reject(err));
60
+ });
61
+
62
+ file.on('error', (err) => {
63
+ fs.unlink(dest, () => reject(err));
64
+ });
65
+ });
66
+ }
10
67
 
11
68
  export async function getLatestVersion(packageName: string): Promise<string> {
12
- return execSync(`npm view ${packageName} version --tag=latest`)
13
- .toString()
14
- .trim();
69
+ return execSync(`npm view ${packageName} version --tag=latest`).toString().trim();
15
70
  }
16
71
 
17
- export async function loadNpm(packageName: string): Promise<string> {
72
+ export async function loadNpm(packageName: string): Promise<LoadedNpmPkg> {
18
73
  const packageLatestVersion = await getLatestVersion(packageName);
19
74
 
20
- const packagePath = path.join(
21
- __dirname,
22
- `./.download/${packageName}-${packageLatestVersion}`,
23
- );
75
+ const packagePath = path.join(__dirname, `./.download/${packageName}-${packageLatestVersion}`);
24
76
 
25
77
  if (existsSync(packagePath)) {
26
- return packagePath;
78
+ return new LoadedNpmPkg(packageName, packageLatestVersion, packagePath);
27
79
  }
28
80
 
29
- // download else
30
81
  try {
31
- const tarballUrl = execSync(
32
- `npm view ${packageName}@${packageLatestVersion} dist.tarball`,
33
- )
82
+ // 获取 tarball 地址
83
+ const tarballUrl = execSync(`npm view ${packageName}@${packageLatestVersion} dist.tarball`)
34
84
  .toString()
35
85
  .trim();
36
- await download(tarballUrl, packagePath, { extract: true, strip: 1 });
37
- return packagePath;
86
+
87
+ // 临时 tarball 路径
88
+ const tempTarballPath = path.join(
89
+ __dirname,
90
+ `./.download/${packageName}-${packageLatestVersion}.tgz`
91
+ );
92
+
93
+ // 确保目录存在
94
+ fs.ensureDirSync(path.dirname(tempTarballPath));
95
+
96
+ // 下载 tarball
97
+ await downloadFile(tarballUrl, tempTarballPath);
98
+
99
+ fs.ensureDirSync(packagePath);
100
+
101
+ // 解压到目标目录
102
+ await tar.x({
103
+ file: tempTarballPath,
104
+ cwd: packagePath,
105
+ strip: 1,
106
+ });
107
+
108
+ // 删除临时文件
109
+ fs.unlinkSync(tempTarballPath);
110
+
111
+ return new LoadedNpmPkg(packageName, packageLatestVersion, packagePath);
38
112
  } catch (error) {
39
113
  console.error(`Error downloading or extracting package: ${error}`);
40
114
  throw error;
41
115
  }
42
116
  }
43
-
@@ -3,10 +3,12 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { existsSync, readFileSync, writeFileSync } from "fs";
7
- import path from "path";
8
- import { getLatestVersion } from "./npm";
9
- import chalk from "chalk";
6
+ import path from 'path';
7
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
8
+
9
+ import chalk from 'chalk';
10
+
11
+ import { getLatestVersion } from './npm';
10
12
 
11
13
  interface PackageJson {
12
14
  dependencies: { [key: string]: string };
@@ -32,26 +34,23 @@ export class Project {
32
34
  // get nearest package.json
33
35
  let projectPath: string = process.cwd();
34
36
 
35
- while (
36
- projectPath !== "/" &&
37
- !existsSync(path.join(projectPath, "package.json"))
38
- ) {
39
- projectPath = path.join(projectPath, "..");
37
+ while (projectPath !== '/' && !existsSync(path.join(projectPath, 'package.json'))) {
38
+ projectPath = path.join(projectPath, '..');
40
39
  }
41
- if (projectPath === "/") {
42
- throw new Error("Please run this command in a valid project");
40
+ if (projectPath === '/') {
41
+ throw new Error('Please run this command in a valid project');
43
42
  }
44
43
 
45
44
  this.projectPath = projectPath;
46
45
 
47
- this.srcPath = path.join(projectPath, "src");
48
- this.packageJsonPath = path.join(projectPath, "package.json");
49
- this.packageJson = JSON.parse(readFileSync(this.packageJsonPath, "utf8"));
46
+ this.srcPath = path.join(projectPath, 'src');
47
+ this.packageJsonPath = path.join(projectPath, 'package.json');
48
+ this.packageJson = JSON.parse(readFileSync(this.packageJsonPath, 'utf8'));
50
49
 
51
50
  this.flowgramVersion =
52
- this.packageJson.dependencies["@flowgram.ai/fixed-layout-editor"] ||
53
- this.packageJson.dependencies["@flowgram.ai/free-layout-editor"] ||
54
- this.packageJson.dependencies["@flowgram.ai/editor"];
51
+ this.packageJson.dependencies['@flowgram.ai/fixed-layout-editor'] ||
52
+ this.packageJson.dependencies['@flowgram.ai/free-layout-editor'] ||
53
+ this.packageJson.dependencies['@flowgram.ai/editor'];
55
54
  }
56
55
 
57
56
  async addDependency(dependency: string) {
@@ -59,7 +58,7 @@ export class Project {
59
58
  let version: string;
60
59
 
61
60
  // 处理作用域包(如 @types/react@1.0.0)
62
- const lastAtIndex = dependency.lastIndexOf("@");
61
+ const lastAtIndex = dependency.lastIndexOf('@');
63
62
 
64
63
  if (lastAtIndex <= 0) {
65
64
  // 没有@符号 或者@在开头(如 @types/react)
@@ -77,10 +76,7 @@ export class Project {
77
76
  }
78
77
 
79
78
  this.packageJson.dependencies[name] = version;
80
- writeFileSync(
81
- this.packageJsonPath,
82
- JSON.stringify(this.packageJson, null, 2),
83
- );
79
+ writeFileSync(this.packageJsonPath, JSON.stringify(this.packageJson, null, 2));
84
80
  }
85
81
 
86
82
  async addDependencies(dependencies: string[]) {
@@ -90,14 +86,11 @@ export class Project {
90
86
  }
91
87
 
92
88
  writeToPackageJsonFile() {
93
- writeFileSync(
94
- this.packageJsonPath,
95
- JSON.stringify(this.packageJson, null, 2),
96
- );
89
+ writeFileSync(this.packageJsonPath, JSON.stringify(this.packageJson, null, 2));
97
90
  }
98
91
 
99
92
  printInfo() {
100
- console.log(chalk.bold("Project Info:"));
93
+ console.log(chalk.bold('Project Info:'));
101
94
  console.log(chalk.black(` - Flowgram Version: ${this.flowgramVersion}`));
102
95
  console.log(chalk.black(` - Project Path: ${this.projectPath}`));
103
96
  }