@flowgram.ai/cli 0.1.8 β†’ 0.4.11

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.1.8",
3
+ "version": "0.4.11",
4
4
  "description": "A CLI tool to create demo projects or sync materials",
5
5
  "bin": {
6
6
  "flowgram-cli": "./bin/index.js"
@@ -17,7 +17,8 @@
17
17
  "chalk": "^5.3.0",
18
18
  "download": "8.0.0",
19
19
  "tar": "7.4.3",
20
- "inquirer": "^9.2.7"
20
+ "inquirer": "^9.2.7",
21
+ "ignore": "~7.0.5"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/download": "8.0.5",
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { Command } from "commander";
7
7
 
8
8
  import { syncMaterial } from "./materials";
9
9
  import { createApp } from "./create-app";
10
+ import { updateFlowgramVersion } from "./update-version";
10
11
 
11
12
  const program = new Command();
12
13
 
@@ -15,7 +16,7 @@ program.name("flowgram-cli").version("1.0.0").description("Flowgram CLI");
15
16
  program
16
17
  .command("create-app")
17
18
  .description("Create a new flowgram project")
18
- .argument('[string]', 'Project name')
19
+ .argument("[string]", "Project name")
19
20
  .action(async (projectName) => {
20
21
  await createApp(projectName);
21
22
  });
@@ -23,9 +24,25 @@ program
23
24
  program
24
25
  .command("materials")
25
26
  .description("Sync materials to the project")
26
- .argument('[string]', 'Material name')
27
- .action(async (materialName) => {
28
- await syncMaterial(materialName);
27
+ .argument("[string]", "Material name")
28
+ .option(
29
+ "--refresh-project-imports",
30
+ "Refresh project imports to copied materials",
31
+ false,
32
+ )
33
+ .action(async (materialName, options) => {
34
+ await syncMaterial({
35
+ materialName,
36
+ refreshProjectImports: options.refreshProjectImports,
37
+ });
38
+ });
39
+
40
+ program
41
+ .command("update-version")
42
+ .description("Update flowgram version in the project")
43
+ .argument("[string]", "Flowgram version")
44
+ .action(async (version) => {
45
+ await updateFlowgramVersion(version);
29
46
  });
30
47
 
31
48
  program.parse(process.argv);
@@ -10,17 +10,25 @@ import { copyMaterial, listAllMaterials, Material } from "./materials";
10
10
  import { loadNpm } from "../utils/npm";
11
11
  import path from "path";
12
12
  import { Project } from "../utils/project";
13
+ import { executeRefreshProjectImport } from "./refresh-project-import";
14
+
15
+ export async function syncMaterial(opts: {
16
+ materialName?: string;
17
+ refreshProjectImports?: boolean;
18
+ }) {
19
+ const { materialName, refreshProjectImports } = opts;
13
20
 
14
- export async function syncMaterial(materialName?: string) {
15
21
  // materialName can be undefined
16
- console.log(chalk.bgGreenBright("Welcome to @flowgram.ai form-materials!"));
22
+ console.log(chalk.bold("πŸš€ Welcome to @flowgram.ai form-materials!"));
17
23
 
18
24
  const project = await Project.getSingleton();
19
25
  project.printInfo();
20
26
 
21
27
  if (!project.flowgramVersion) {
22
28
  throw new Error(
23
- "Please install @flowgram.ai/fixed-layout-editor or @flowgram.ai/free-layout-editor",
29
+ chalk.red(
30
+ "❌ Please install @flowgram.ai/fixed-layout-editor or @flowgram.ai/free-layout-editor",
31
+ ),
24
32
  );
25
33
  }
26
34
 
@@ -31,7 +39,7 @@ export async function syncMaterial(materialName?: string) {
31
39
 
32
40
  let material: Material | undefined; // material can be undefined
33
41
 
34
- // Check if materialName is provided and exists in materials
42
+ // 1. Check if materialName is provided and exists in materials
35
43
  if (materialName) {
36
44
  const selectedMaterial = materials.find(
37
45
  (m) => `${m.type}/${m.name}` === materialName,
@@ -48,7 +56,7 @@ export async function syncMaterial(materialName?: string) {
48
56
  }
49
57
  }
50
58
 
51
- // If material not found or materialName not provided, prompt user to select
59
+ // 2. If material not found or materialName not provided, prompt user to select
52
60
  if (!material) {
53
61
  // User select one component
54
62
  const result = await inquirer.prompt<{
@@ -74,9 +82,15 @@ export async function syncMaterial(materialName?: string) {
74
82
  process.exit(1);
75
83
  }
76
84
 
85
+ // 3. Refresh project imports
86
+ if (refreshProjectImports) {
87
+ console.log(chalk.bold("πŸš€ Refresh imports in your project"));
88
+ executeRefreshProjectImport(project, material);
89
+ }
90
+
77
91
  // 4. Copy the materials to the project
78
92
  console.log(
79
- chalk.bold("The following materials will be added to your project"),
93
+ chalk.bold("πŸš€ The following materials will be added to your project"),
80
94
  );
81
95
  console.log(material);
82
96
  let { packagesToInstall } = copyMaterial(material, project, formMaterialPath);
@@ -84,10 +98,10 @@ export async function syncMaterial(materialName?: string) {
84
98
  // 4. Install the dependencies
85
99
  await project.addDependencies(packagesToInstall);
86
100
  console.log(
87
- chalk.bold("These npm dependencies is added to your package.json"),
101
+ chalk.bold("βœ… These npm dependencies is added to your package.json"),
88
102
  );
89
103
  packagesToInstall.forEach((_package) => {
90
104
  console.log(`- ${_package}`);
91
- })
92
- console.log(chalk.bold("\nPlease run npm install to install dependencies"));
105
+ });
106
+ console.log(chalk.bold("\n➑️ Please run npm install to install dependencies\n"));
93
107
  }
@@ -3,16 +3,11 @@
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
5
 
6
- import { fileURLToPath } from "url";
7
6
  import path from "path";
8
7
  import fs from "fs";
9
8
 
10
- import { traverseRecursiveFiles } from "../utils/traverse-file";
11
- import { replaceImport, traverseFileImports } from "../utils/import";
12
9
  import { Project } from "../utils/project"; // Import ProjectInfo
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
10
+ import { traverseRecursiveTsFiles } from "../utils/ts-file";
16
11
 
17
12
  // Added type definitions
18
13
  export interface Material {
@@ -77,7 +72,8 @@ export const copyMaterial = (
77
72
  ): {
78
73
  packagesToInstall: string[];
79
74
  } => {
80
- const formMaterialDependencies = getFormMaterialDependencies(formMaterialPath);
75
+ const formMaterialDependencies =
76
+ getFormMaterialDependencies(formMaterialPath);
81
77
 
82
78
  const sourceDir: string = material.path;
83
79
  const materialRoot: string = path.join(
@@ -91,38 +87,35 @@ export const copyMaterial = (
91
87
 
92
88
  fs.cpSync(sourceDir, targetDir, { recursive: true });
93
89
 
94
- for (const file of traverseRecursiveFiles(targetDir)) {
95
- if ([".ts", ".tsx"].includes(file.suffix)) {
96
- for (const importDeclaration of traverseFileImports(file.content)) {
97
- const { source } = importDeclaration;
98
-
99
- if (source.startsWith("@/")) {
100
- // is inner import
101
- console.log(
102
- `Replace Import from ${source} to @flowgram.ai/form-materials`,
103
- );
104
- file.replace((content) =>
105
- replaceImport(content, importDeclaration, [
106
- { ...importDeclaration, source: "@flowgram.ai/form-materials" },
107
- ]),
108
- );
109
- packagesToInstall.add(
110
- `@flowgram.ai/form-materials@${project.flowgramVersion}`,
111
- );
112
- } else if (!source.startsWith(".") && !source.startsWith("react")) {
113
- // check if is in form material dependencies
114
- const [dep, version] =
115
- Object.entries(formMaterialDependencies).find(([_key]) =>
116
- source.startsWith(_key),
117
- ) || [];
118
- if (!dep) {
119
- continue;
120
- }
121
- if (dep.startsWith("@flowgram.ai/")) {
122
- packagesToInstall.add(`${dep}@${project.flowgramVersion}`);
123
- } else {
124
- packagesToInstall.add(`${dep}@${version}`);
125
- }
90
+ for (const file of traverseRecursiveTsFiles(targetDir)) {
91
+ for (const importDeclaration of file.imports) {
92
+ const { source } = importDeclaration;
93
+
94
+ if (source.startsWith("@/")) {
95
+ // is inner import
96
+ console.log(
97
+ `Replace Import from ${source} to @flowgram.ai/form-materials`,
98
+ );
99
+ file.replaceImport(
100
+ [importDeclaration],
101
+ [{ ...importDeclaration, source: "@flowgram.ai/form-materials" }],
102
+ );
103
+ packagesToInstall.add(
104
+ `@flowgram.ai/form-materials@${project.flowgramVersion}`,
105
+ );
106
+ } else if (!source.startsWith(".") && !source.startsWith("react")) {
107
+ // check if is in form material dependencies
108
+ const [dep, version] =
109
+ Object.entries(formMaterialDependencies).find(([_key]) =>
110
+ source.startsWith(_key),
111
+ ) || [];
112
+ if (!dep) {
113
+ continue;
114
+ }
115
+ if (dep.startsWith("@flowgram.ai/")) {
116
+ packagesToInstall.add(`${dep}@${project.flowgramVersion}`);
117
+ } else {
118
+ packagesToInstall.add(`${dep}@${version}`);
126
119
  }
127
120
  }
128
121
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
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
+ }
22
+
23
+ const targetDir = `@/form-materials/${material.type}/${material.name}`;
24
+
25
+ const exportNames = materialFile.allExportNames;
26
+
27
+ console.log(`πŸ‘€ The exports of ${material.name} is ${exportNames.join(",")}`);
28
+
29
+ for (const tsFile of traverseRecursiveTsFiles(project.srcPath)) {
30
+ 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) {
36
+ continue;
37
+ }
38
+ const nextImports: ImportDeclaration[] = [
39
+ {
40
+ ...importDeclaration,
41
+ namedImports: currentMaterialImports,
42
+ source: targetDir,
43
+ },
44
+ ];
45
+
46
+ const keepImportNames = importDeclaration.namedImports?.filter(
47
+ (item) => !exportNames.includes(item.imported),
48
+ );
49
+
50
+ if (keepImportNames?.length) {
51
+ nextImports.unshift({
52
+ ...importDeclaration,
53
+ namedImports: keepImportNames,
54
+ });
55
+ }
56
+
57
+ tsFile.replaceImport([importDeclaration], nextImports);
58
+ console.log(chalk.green(`πŸ”„ Refresh Imports In: ${tsFile.path}`));
59
+
60
+ console.log(
61
+ `From:\n${importDeclaration.statement}\nTo:\n${nextImports
62
+ .map((item) => item.statement)
63
+ .join("\n")}`,
64
+ );
65
+ }
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { getLatestVersion } from "../utils/npm";
7
+ import { traverseRecursiveFiles } from "../utils/file";
8
+ import chalk from "chalk";
9
+
10
+ export async function updateFlowgramVersion(inputVersion?: string) {
11
+ console.log(chalk.bold("πŸš€ Welcome to @flowgram.ai update-version helper"));
12
+
13
+ // Get latest version
14
+ const latestVersion = await getLatestVersion("@flowgram.ai/editor");
15
+ const currentPath = process.cwd();
16
+ console.log("- Latest flowgram version: ", latestVersion);
17
+ console.log("- Current Path: ", currentPath);
18
+
19
+ // User Input flowgram version, default is latestVersion
20
+ const flowgramVersion: string = inputVersion || latestVersion;
21
+
22
+ for (const file of traverseRecursiveFiles(currentPath)) {
23
+ if (file.path.endsWith("package.json")) {
24
+ console.log("πŸ‘€ Find package.json: ", file.path);
25
+ let updated = false;
26
+ const json = JSON.parse(file.content);
27
+ if (json.dependencies) {
28
+ for (const key in json.dependencies) {
29
+ if (key.startsWith("@flowgram.ai/")) {
30
+ updated = true;
31
+ json.dependencies[key] = flowgramVersion;
32
+ console.log(`- Update ${key} to ${flowgramVersion}`);
33
+ }
34
+ }
35
+ }
36
+ if (json.devDependencies) {
37
+ for (const key in json.devDependencies) {
38
+ if (key.startsWith("@flowgram.ai/")) {
39
+ updated = true;
40
+ json.devDependencies[key] = flowgramVersion;
41
+ console.log(`- Update ${key} to ${flowgramVersion}`);
42
+ }
43
+ }
44
+ }
45
+
46
+ if (updated) {
47
+ file.write(JSON.stringify(json, null, 2));
48
+ console.log(`βœ… ${file.path} Updated`);
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export function extractNamedExports(content: string) {
7
+ const valueExports = [];
8
+ const typeExports = [];
9
+
10
+ // Collect all type definition names
11
+ const typeDefinitions = new Set();
12
+ const typePatterns = [
13
+ /\b(?:type|interface)\s+(\w+)/g,
14
+ /\bexport\s+(?:type|interface)\s+(\w+)/g,
15
+ ];
16
+
17
+ let match;
18
+ for (const pattern of typePatterns) {
19
+ while ((match = pattern.exec(content)) !== null) {
20
+ typeDefinitions.add(match[1]);
21
+ }
22
+ }
23
+
24
+ // Match various export patterns
25
+ const exportPatterns = [
26
+ // export const/var/let/function/class/type/interface
27
+ /\bexport\s+(const|var|let|function|class|type|interface)\s+(\w+)/g,
28
+ // export { name1, name2 }
29
+ /\bexport\s*\{([^}]+)\}/g,
30
+ // export { name as alias }
31
+ /\bexport\s*\{[^}]*\b(\w+)\s+as\s+(\w+)[^}]*\}/g,
32
+ // export default function name()
33
+ /\bexport\s+default\s+(?:function|class)\s+(\w+)/g,
34
+ // export type { Type1, Type2 }
35
+ /\bexport\s+type\s*\{([^}]+)\}/g,
36
+ // export type { Original as Alias }
37
+ /\bexport\s+type\s*\{[^}]*\b(\w+)\s+as\s+(\w+)[^}]*\}/g,
38
+ ];
39
+
40
+ // Handle first pattern: export const/var/let/function/class/type/interface
41
+ exportPatterns[0].lastIndex = 0;
42
+ while ((match = exportPatterns[0].exec(content)) !== null) {
43
+ const [, kind, name] = match;
44
+ if (kind === "type" || kind === "interface" || typeDefinitions.has(name)) {
45
+ typeExports.push(name);
46
+ } else {
47
+ valueExports.push(name);
48
+ }
49
+ }
50
+
51
+ // Handle second pattern: export { name1, name2 }
52
+ exportPatterns[1].lastIndex = 0;
53
+ while ((match = exportPatterns[1].exec(content)) !== null) {
54
+ const exportsList = match[1]
55
+ .split(",")
56
+ .map((item) => item.trim())
57
+ .filter((item) => item && !item.includes(" as "));
58
+
59
+ for (const name of exportsList) {
60
+ if (name.startsWith("type ")) {
61
+ typeExports.push(name.replace("type ", "").trim());
62
+ } else if (typeDefinitions.has(name)) {
63
+ typeExports.push(name);
64
+ } else {
65
+ valueExports.push(name);
66
+ }
67
+ }
68
+ }
69
+
70
+ // Handle third pattern: export { name as alias }
71
+ exportPatterns[2].lastIndex = 0;
72
+ while ((match = exportPatterns[2].exec(content)) !== null) {
73
+ const [, original, alias] = match;
74
+ if (typeDefinitions.has(original)) {
75
+ typeExports.push(alias);
76
+ } else {
77
+ valueExports.push(alias);
78
+ }
79
+ }
80
+
81
+ // Handle fourth pattern: export default function name()
82
+ exportPatterns[3].lastIndex = 0;
83
+ while ((match = exportPatterns[3].exec(content)) !== null) {
84
+ const name = match[1];
85
+ if (typeDefinitions.has(name)) {
86
+ typeExports.push(name);
87
+ } else {
88
+ valueExports.push(name);
89
+ }
90
+ }
91
+
92
+ // Handle fifth pattern: export type { Type1, Type2 }
93
+ exportPatterns[4].lastIndex = 0;
94
+ while ((match = exportPatterns[4].exec(content)) !== null) {
95
+ const exportsList = match[1]
96
+ .split(",")
97
+ .map((item) => item.trim())
98
+ .filter((item) => item && !item.includes(" as "));
99
+
100
+ for (const name of exportsList) {
101
+ typeExports.push(name);
102
+ }
103
+ }
104
+
105
+ // Handle sixth pattern: export type { Original as Alias }
106
+ exportPatterns[5].lastIndex = 0;
107
+ while ((match = exportPatterns[5].exec(content)) !== null) {
108
+ const [, original, alias] = match;
109
+ typeExports.push(alias);
110
+ }
111
+
112
+ // Deduplicate and sort
113
+ return {
114
+ values: [...new Set(valueExports)].sort(),
115
+ types: [...new Set(typeExports)].sort(),
116
+ };
117
+ }
@@ -0,0 +1,87 @@
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
+ import ignore, { Ignore } from "ignore";
9
+
10
+ export class File {
11
+ content: string;
12
+
13
+ isUtf8: boolean;
14
+
15
+ relativePath: string;
16
+
17
+ path: string;
18
+
19
+ suffix: string;
20
+
21
+ constructor(filePath: string, public root: string = "/") {
22
+ this.path = filePath;
23
+ this.relativePath = path.relative(this.root, this.path);
24
+ this.suffix = path.extname(this.path);
25
+
26
+ // Check if exists
27
+ if (!fs.existsSync(this.path)) {
28
+ throw Error(`File ${path} Not Exists`);
29
+ }
30
+
31
+ // If no utf-8, skip
32
+ try {
33
+ this.content = fs.readFileSync(this.path, "utf-8");
34
+ this.isUtf8 = true;
35
+ } catch (e) {
36
+ this.isUtf8 = false;
37
+ return;
38
+ }
39
+ }
40
+
41
+ replace(updater: (content: string) => string) {
42
+ if (!this.isUtf8) {
43
+ console.warn("Not UTF-8 file skipped: ", this.path);
44
+ return;
45
+ }
46
+ this.content = updater(this.content);
47
+ fs.writeFileSync(this.path, this.content, "utf-8");
48
+ }
49
+
50
+ write(nextContent: string) {
51
+ this.content = nextContent;
52
+ fs.writeFileSync(this.path, this.content, "utf-8");
53
+ }
54
+ }
55
+
56
+ export function* traverseRecursiveFilePaths(
57
+ folder: string,
58
+ ig: Ignore = ignore().add(".git"),
59
+ root: string = folder,
60
+ ): Generator<string> {
61
+ const files = fs.readdirSync(folder);
62
+
63
+ // 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"));
66
+ }
67
+
68
+ for (const file of files) {
69
+ const filePath = path.join(folder, file);
70
+
71
+ if (ig.ignores(path.relative(root, filePath))) {
72
+ continue;
73
+ }
74
+
75
+ if (fs.statSync(filePath).isDirectory()) {
76
+ yield* traverseRecursiveFilePaths(filePath, ig, root);
77
+ } else {
78
+ yield filePath;
79
+ }
80
+ }
81
+ }
82
+
83
+ export function* traverseRecursiveFiles(folder: string): Generator<File> {
84
+ for (const filePath of traverseRecursiveFilePaths(folder)) {
85
+ yield new File(filePath);
86
+ }
87
+ }
@@ -11,7 +11,7 @@
11
11
  * import D, { type E, F } from 'module';
12
12
  * import A, { B as B1 } from 'module';
13
13
  */
14
- interface ImportDeclaration {
14
+ export interface ImportDeclaration {
15
15
  // origin statement
16
16
  statement: string;
17
17
 
@@ -50,7 +50,7 @@ export function assembleImport(declaration: ImportDeclaration): string {
50
50
  if (namespaceImport) {
51
51
  importClauses.push(`* as ${namespaceImport}`);
52
52
  }
53
- return `import ${importClauses.join(', ')} from '${source}'`;
53
+ return `import ${importClauses.join(', ')} from '${source}';`;
54
54
  }
55
55
 
56
56
  export function replaceImport(
@@ -66,7 +66,7 @@ export function replaceImport(
66
66
  export function* traverseFileImports(fileContent: string): Generator<ImportDeclaration> {
67
67
  // εŒΉι…ζ‰€ζœ‰ import θ―­ε₯ηš„ζ­£εˆ™θ‘¨θΎΎεΌ
68
68
  const importRegex =
69
- /import\s+([^{}*,]*?)?(?:\s*\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,?)?(?:\s*\{([^}]*)\}\s*,?)?(?:\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,?)?\s*from\s*['"`]([^'"`]+)['"`]/g;
69
+ /import\s+([^{}*,]*?)?(?:\s*\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,?)?(?:\s*\{([^}]*)\}\s*,?)?(?:\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,?)?\s*from\s*['"`]([^'"`]+)['"`]\;?/g;
70
70
 
71
71
  let match;
72
72
  while ((match = importRegex.exec(fileContent)) !== null) {
@@ -24,6 +24,8 @@ export class Project {
24
24
 
25
25
  packageJson: PackageJson;
26
26
 
27
+ srcPath: string;
28
+
27
29
  protected constructor() {}
28
30
 
29
31
  async init() {
@@ -42,6 +44,7 @@ export class Project {
42
44
 
43
45
  this.projectPath = projectPath;
44
46
 
47
+ this.srcPath = path.join(projectPath, "src");
45
48
  this.packageJsonPath = path.join(projectPath, "package.json");
46
49
  this.packageJson = JSON.parse(readFileSync(this.packageJsonPath, "utf8"));
47
50
 
@@ -86,6 +89,13 @@ export class Project {
86
89
  }
87
90
  }
88
91
 
92
+ writeToPackageJsonFile() {
93
+ writeFileSync(
94
+ this.packageJsonPath,
95
+ JSON.stringify(this.packageJson, null, 2),
96
+ );
97
+ }
98
+
89
99
  printInfo() {
90
100
  console.log(chalk.bold("Project Info:"));
91
101
  console.log(chalk.black(` - Flowgram Version: ${this.flowgramVersion}`));