@flowgram.ai/form-materials 0.1.0-alpha.11 → 0.1.0-alpha.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.
Files changed (133) hide show
  1. package/bin/index.ts +14 -22
  2. package/bin/materials.ts +41 -90
  3. package/bin/project.ts +4 -0
  4. package/bin/utils/import.ts +127 -0
  5. package/bin/utils/traverse-file.ts +60 -0
  6. package/dist/esm/index.js +2073 -1601
  7. package/dist/esm/index.js.map +1 -1
  8. package/dist/index.d.mts +392 -131
  9. package/dist/index.d.ts +392 -131
  10. package/dist/index.js +2175 -1710
  11. package/dist/index.js.map +1 -1
  12. package/package.json +5 -4
  13. package/src/components/assign-row/components/blur-input.tsx +27 -0
  14. package/src/components/assign-row/index.tsx +84 -0
  15. package/src/components/assign-row/types.ts +25 -0
  16. package/src/components/assign-rows/index.tsx +59 -0
  17. package/src/components/batch-outputs/index.tsx +7 -14
  18. package/src/components/batch-outputs/types.ts +1 -1
  19. package/src/components/batch-variable-selector/index.tsx +2 -2
  20. package/src/components/code-editor/index.tsx +7 -0
  21. package/src/components/code-editor/language-features.ts +22 -1
  22. package/src/components/code-editor/theme/light.ts +1 -1
  23. package/src/components/code-editor-mini/index.tsx +31 -0
  24. package/src/components/condition-row/constants.ts +8 -10
  25. package/src/components/condition-row/hooks/useOp.tsx +15 -9
  26. package/src/components/condition-row/hooks/useRule.ts +9 -5
  27. package/src/components/condition-row/index.tsx +28 -10
  28. package/src/components/condition-row/types.ts +5 -5
  29. package/src/components/constant-input/index.tsx +20 -61
  30. package/src/components/constant-input/types.ts +6 -9
  31. package/src/components/display-flow-value/index.tsx +59 -0
  32. package/src/components/display-inputs-values/index.tsx +28 -0
  33. package/src/components/display-inputs-values/styles.ts +12 -0
  34. package/src/components/display-outputs/index.tsx +65 -0
  35. package/src/components/display-outputs/styles.ts +12 -0
  36. package/src/components/display-schema-tag/index.tsx +45 -0
  37. package/src/components/display-schema-tag/styles.ts +28 -0
  38. package/src/components/display-schema-tree/index.tsx +75 -0
  39. package/src/components/display-schema-tree/styles.tsx +90 -0
  40. package/src/components/dynamic-value-input/hooks.ts +53 -0
  41. package/src/components/dynamic-value-input/index.tsx +74 -19
  42. package/src/components/dynamic-value-input/styles.tsx +28 -2
  43. package/src/components/index.ts +9 -0
  44. package/src/components/inputs-values/components/blur-input.tsx +27 -0
  45. package/src/components/inputs-values/index.tsx +75 -0
  46. package/src/components/inputs-values/styles.tsx +19 -0
  47. package/src/components/inputs-values/types.ts +22 -0
  48. package/src/components/json-editor-with-variables/extensions/variable-tree.tsx +1 -1
  49. package/src/components/json-editor-with-variables/index.tsx +2 -1
  50. package/src/components/json-schema-editor/default-value.tsx +12 -106
  51. package/src/components/json-schema-editor/hooks.tsx +53 -94
  52. package/src/components/json-schema-editor/index.tsx +32 -13
  53. package/src/components/json-schema-editor/styles.tsx +0 -29
  54. package/src/components/json-schema-editor/types.ts +1 -1
  55. package/src/components/prompt-editor/types.tsx +1 -1
  56. package/src/components/prompt-editor-with-inputs/extensions/inputs-tree.tsx +2 -1
  57. package/src/components/prompt-editor-with-inputs/index.tsx +3 -2
  58. package/src/components/prompt-editor-with-inputs/inputs-picker.tsx +2 -2
  59. package/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx +6 -3
  60. package/src/components/prompt-editor-with-variables/extensions/variable-tree.tsx +1 -1
  61. package/src/components/prompt-editor-with-variables/index.tsx +2 -1
  62. package/src/components/type-selector/index.tsx +58 -13
  63. package/src/components/variable-selector/index.tsx +42 -17
  64. package/src/components/variable-selector/styles.tsx +18 -8
  65. package/src/components/variable-selector/use-variable-tree.tsx +19 -22
  66. package/src/effects/auto-rename-ref/index.ts +1 -1
  67. package/src/effects/index.ts +3 -1
  68. package/src/effects/listen-ref-schema-change/index.ts +56 -0
  69. package/src/effects/listen-ref-value-change/index.ts +53 -0
  70. package/src/effects/provide-batch-input/index.ts +1 -1
  71. package/src/effects/provide-json-schema-outputs/index.ts +1 -3
  72. package/src/effects/sync-variable-title/index.ts +1 -0
  73. package/src/effects/validate-when-variable-sync/index.ts +35 -0
  74. package/src/form-plugins/batch-outputs-plugin/index.ts +1 -1
  75. package/src/form-plugins/index.ts +3 -1
  76. package/src/form-plugins/infer-assign-plugin/index.ts +90 -0
  77. package/src/form-plugins/infer-inputs-plugin/index.ts +108 -0
  78. package/src/hooks/index.tsx +6 -0
  79. package/src/hooks/use-object-list/index.tsx +136 -0
  80. package/src/index.ts +3 -1
  81. package/src/{utils/format-legacy-refs → plugins/disable-declaration-plugin}/config.json +1 -1
  82. package/src/plugins/disable-declaration-plugin/create-disable-declaration-plugin.ts +31 -0
  83. package/src/plugins/disable-declaration-plugin/index.tsx +6 -0
  84. package/src/plugins/index.ts +7 -0
  85. package/src/plugins/json-schema-preset/config.json +9 -0
  86. package/src/plugins/json-schema-preset/create-type-preset-plugin.tsx +28 -0
  87. package/src/plugins/json-schema-preset/index.tsx +41 -0
  88. package/src/plugins/json-schema-preset/manager.ts +18 -0
  89. package/src/plugins/json-schema-preset/type-definition/array.tsx +26 -0
  90. package/src/plugins/json-schema-preset/type-definition/boolean.tsx +33 -0
  91. package/src/plugins/json-schema-preset/type-definition/index.tsx +24 -0
  92. package/src/plugins/json-schema-preset/type-definition/integer.tsx +25 -0
  93. package/src/plugins/json-schema-preset/type-definition/number.tsx +25 -0
  94. package/src/plugins/json-schema-preset/type-definition/object.tsx +26 -0
  95. package/src/plugins/json-schema-preset/type-definition/string.tsx +24 -0
  96. package/src/{utils → shared}/index.ts +1 -1
  97. package/src/shared/inject-material/README.md +170 -0
  98. package/src/shared/inject-material/README.zh.md +174 -0
  99. package/src/shared/inject-material/index.tsx +87 -0
  100. package/src/typings/flow-value/index.ts +11 -0
  101. package/src/typings/index.ts +0 -1
  102. package/src/validate/index.tsx +6 -0
  103. package/src/validate/validate-flow-value/index.tsx +73 -0
  104. package/src/components/batch-outputs/config.json +0 -12
  105. package/src/components/batch-outputs/use-list.ts +0 -86
  106. package/src/components/batch-variable-selector/config.json +0 -5
  107. package/src/components/code-editor/config.json +0 -10
  108. package/src/components/condition-row/config.json +0 -5
  109. package/src/components/constant-input/config.json +0 -6
  110. package/src/components/dynamic-value-input/config.json +0 -5
  111. package/src/components/json-editor-with-variables/config.json +0 -13
  112. package/src/components/json-schema-editor/config.json +0 -13
  113. package/src/components/json-schema-editor/utils.ts +0 -29
  114. package/src/components/prompt-editor/config.json +0 -9
  115. package/src/components/prompt-editor-with-inputs/config.json +0 -13
  116. package/src/components/prompt-editor-with-variables/config.json +0 -13
  117. package/src/components/type-selector/config.json +0 -5
  118. package/src/components/type-selector/constants.tsx +0 -364
  119. package/src/components/variable-selector/config.json +0 -5
  120. package/src/effects/auto-rename-ref/config.json +0 -5
  121. package/src/effects/provide-batch-input/config.json +0 -5
  122. package/src/effects/provide-batch-outputs/config.json +0 -5
  123. package/src/effects/provide-batch-outputs/index.ts +0 -38
  124. package/src/effects/provide-json-schema-outputs/config.json +0 -8
  125. package/src/effects/sync-variable-title/config.json +0 -5
  126. package/src/form-plugins/batch-outputs-plugin/config.json +0 -7
  127. package/src/typings/flow-value/config.json +0 -5
  128. package/src/typings/json-schema/config.json +0 -5
  129. package/src/typings/json-schema/index.ts +0 -36
  130. package/src/utils/json-schema/config.json +0 -5
  131. package/src/utils/json-schema/index.ts +0 -180
  132. /package/src/{utils → shared}/format-legacy-refs/index.ts +0 -0
  133. /package/src/{utils → shared}/format-legacy-refs/readme.md +0 -0
package/bin/index.ts CHANGED
@@ -5,7 +5,7 @@ import { Command } from 'commander';
5
5
  import chalk from 'chalk';
6
6
 
7
7
  import { getProjectInfo, installDependencies, ProjectInfo } from './project.js';
8
- import { bfsMaterials, copyMaterial, listAllMaterials, Material } from './materials.js';
8
+ import { copyMaterial, listAllMaterials, Material } from './materials.js';
9
9
 
10
10
  const program = new Command();
11
11
 
@@ -66,34 +66,26 @@ program
66
66
  process.exit(1);
67
67
  }
68
68
 
69
+ // 4. Copy the materials to the project
70
+ console.log(chalk.bold('The following materials will be added to your project'));
69
71
  console.log(material);
70
-
71
- // 3. Get the component dependencies by BFS (include depMaterials and depPackages)
72
- const { allMaterials, allPackages } = bfsMaterials(material!, materials);
72
+ let { packagesToInstall } = copyMaterial(material, projectInfo);
73
73
 
74
74
  // 4. Install the dependencies
75
- let flowgramPackage = `@flowgram.ai/editor`;
76
- if (projectInfo.flowgramVersion !== 'workspace:*') {
77
- flowgramPackage = `@flowgram.ai/editor@${projectInfo.flowgramVersion}`;
78
- }
79
- const packagesToInstall: string[] = [flowgramPackage, ...allPackages];
75
+ packagesToInstall = packagesToInstall.map((_pkg) => {
76
+ if (
77
+ _pkg.startsWith(`@flowgram.ai/`) &&
78
+ projectInfo.flowgramVersion !== 'workspace:*' &&
79
+ !_pkg.endsWith(`@${projectInfo.flowgramVersion}`)
80
+ ) {
81
+ return `${_pkg}@${projectInfo.flowgramVersion}`;
82
+ }
83
+ return _pkg;
84
+ });
80
85
 
81
86
  console.log(chalk.bold('These npm dependencies will be added to your project'));
82
87
  console.log(packagesToInstall);
83
88
  installDependencies(packagesToInstall, projectInfo);
84
-
85
- // 5. Copy the materials to the project
86
- console.log(chalk.bold('These Materials will be added to your project'));
87
- console.log(allMaterials);
88
- copyMaterial(material, projectInfo, { overwrite: true });
89
-
90
- allMaterials.forEach((mat: Material) => {
91
- if (mat === material) {
92
- return;
93
- }
94
- // Add type for mat
95
- copyMaterial(mat, projectInfo, { overwrite: false });
96
- });
97
89
  });
98
90
 
99
91
  program.parse(process.argv);
package/bin/materials.ts CHANGED
@@ -7,6 +7,8 @@ import { fileURLToPath } from 'url';
7
7
  import path from 'path';
8
8
  import fs from 'fs';
9
9
 
10
+ import { traverseRecursiveFiles } from './utils/traverse-file';
11
+ import { replaceImport, traverseFileImports } from './utils/import';
10
12
  import { ProjectInfo } from './project'; // Import ProjectInfo
11
13
 
12
14
  const __filename = fileURLToPath(import.meta.url);
@@ -17,12 +19,19 @@ export interface Material {
17
19
  name: string;
18
20
  type: string;
19
21
  path: string;
20
- depPackages?: string[];
21
- depMaterials?: string[];
22
22
  [key: string]: any; // For other properties from config.json
23
23
  }
24
24
 
25
- const _types: string[] = ['components', 'effects', 'utils', 'typings', 'form-plugins'];
25
+ const _types: string[] = [
26
+ 'components',
27
+ 'effects',
28
+ 'plugins',
29
+ 'shared',
30
+ 'typings',
31
+ 'validate',
32
+ 'form-plugins',
33
+ 'hooks',
34
+ ];
26
35
 
27
36
  export function listAllMaterials(): Material[] {
28
37
  const _materials: Material[] = [];
@@ -39,19 +48,7 @@ export function listAllMaterials(): Material[] {
39
48
  return null;
40
49
  }
41
50
 
42
- const configPath = path.join(materialsPath, _path, 'config.json');
43
- // Check if config.json exists before reading
44
- if (!fs.existsSync(configPath)) {
45
- console.warn(
46
- `Warning: config.json not found for material at ${path.join(materialsPath, _path)}`
47
- );
48
- return null;
49
- }
50
- const configContent = fs.readFileSync(configPath, 'utf8');
51
- const config = JSON.parse(configContent);
52
-
53
51
  return {
54
- ...config,
55
52
  name: _path, // Assuming the folder name is the material name
56
53
  type: _type,
57
54
  path: path.join(materialsPath, _path),
@@ -64,66 +61,12 @@ export function listAllMaterials(): Material[] {
64
61
  return _materials;
65
62
  }
66
63
 
67
- interface BfsResult {
68
- allMaterials: Material[];
69
- allPackages: string[];
70
- }
71
-
72
- export function bfsMaterials(material: Material, _materials: Material[] = []): BfsResult {
73
- function findConfigByName(name: string): Material | undefined {
74
- return _materials.find(
75
- (_config) => _config.name === name || `${_config.type}/${_config.name}` === name
76
- );
77
- }
78
-
79
- const queue: (Material | undefined)[] = [material]; // Queue can hold undefined if findConfigByName returns undefined
80
- const allMaterials = new Set<Material>();
81
- const allPackages = new Set<string>();
82
-
83
- while (queue.length > 0) {
84
- const _material = queue.shift();
85
- if (!_material || allMaterials.has(_material)) {
86
- // Check if _material is defined
87
- continue;
88
- }
89
- allMaterials.add(_material);
90
-
91
- if (_material.depPackages) {
92
- for (const _package of _material.depPackages) {
93
- allPackages.add(_package);
94
- }
95
- }
96
-
97
- if (_material.depMaterials) {
98
- for (const _materialName of _material.depMaterials) {
99
- const depMaterial = findConfigByName(_materialName);
100
- if (depMaterial) {
101
- // Ensure dependent material is found before adding to queue
102
- queue.push(depMaterial);
103
- } else {
104
- console.warn(
105
- `Warning: Dependent material "${_materialName}" not found for material "${_material.name}".`
106
- );
107
- }
108
- }
109
- }
110
- }
111
-
112
- return {
113
- allMaterials: Array.from(allMaterials),
114
- allPackages: Array.from(allPackages),
115
- };
116
- }
117
-
118
64
  export const copyMaterial = (
119
65
  material: Material,
120
- projectInfo: ProjectInfo,
121
- {
122
- overwrite,
123
- }: {
124
- overwrite?: boolean;
125
- } = {}
126
- ): void => {
66
+ projectInfo: ProjectInfo
67
+ ): {
68
+ packagesToInstall: string[];
69
+ } => {
127
70
  const sourceDir: string = material.path;
128
71
  const materialRoot: string = path.join(
129
72
  projectInfo.projectPath,
@@ -132,25 +75,33 @@ export const copyMaterial = (
132
75
  `${material.type}`
133
76
  );
134
77
  const targetDir = path.join(materialRoot, material.name);
135
-
136
- if (!overwrite && fs.readdirSync(targetDir)?.length > 0) {
137
- console.log(`Material ${material.name} already exists in ${materialRoot}, skip copying.`);
138
- return;
139
- }
78
+ const packagesToInstall: Set<string> = new Set();
140
79
 
141
80
  fs.cpSync(sourceDir, targetDir, { recursive: true });
142
81
 
143
- let materialRootIndexTs: string = '';
144
- const indexTsPath = path.join(materialRoot, 'index.ts');
145
- if (fs.existsSync(indexTsPath)) {
146
- materialRootIndexTs = fs.readFileSync(indexTsPath, 'utf8');
147
- }
148
- if (!materialRootIndexTs.includes(material.name)) {
149
- fs.writeFileSync(
150
- indexTsPath,
151
- `${materialRootIndexTs}${materialRootIndexTs.endsWith('\n') ? '' : '\n'}export * from './${
152
- material.name
153
- }';\n`
154
- );
82
+ for (const file of traverseRecursiveFiles(targetDir)) {
83
+ if (['.ts', '.tsx'].includes(file.suffix)) {
84
+ for (const importDeclaration of traverseFileImports(file.content)) {
85
+ const { source } = importDeclaration;
86
+
87
+ if (source.startsWith('@/')) {
88
+ // is inner import
89
+ console.log(`Replace Import from ${source} to @flowgram.ai/form-materials`);
90
+ file.replace((content) =>
91
+ replaceImport(content, importDeclaration, [
92
+ { ...importDeclaration, source: '@flowgram.ai/form-materials' },
93
+ ])
94
+ );
95
+ packagesToInstall.add('@flowgram.ai/form-materials');
96
+ } else if (!source.startsWith('.') && !source.startsWith('react')) {
97
+ // check if is third party npm packages
98
+ packagesToInstall.add(source);
99
+ }
100
+ }
101
+ }
155
102
  }
103
+
104
+ return {
105
+ packagesToInstall: [...packagesToInstall],
106
+ };
156
107
  };
package/bin/project.ts CHANGED
@@ -70,22 +70,26 @@ export function findRushJson(startPath: string): string | null {
70
70
 
71
71
  export function installDependencies(packages: string[], projectInfo: ProjectInfo): void {
72
72
  if (fs.existsSync(path.join(projectInfo.projectPath, 'yarn.lock'))) {
73
+ console.log(`yarn add ${packages.join(' ')}`);
73
74
  execSync(`yarn add ${packages.join(' ')}`, { stdio: 'inherit' });
74
75
  return;
75
76
  }
76
77
 
77
78
  if (fs.existsSync(path.join(projectInfo.projectPath, 'pnpm-lock.yaml'))) {
79
+ console.log(`pnpm add ${packages.join(' ')}`);
78
80
  execSync(`pnpm add ${packages.join(' ')}`, { stdio: 'inherit' });
79
81
  return;
80
82
  }
81
83
 
82
84
  // rush monorepo
83
85
  if (findRushJson(projectInfo.projectPath)) {
86
+ console.log(`rush add ${packages.map((pkg) => `--package ${pkg}`).join(' ')}`);
84
87
  execSync(`rush add ${packages.map((pkg) => `--package ${pkg}`).join(' ')}`, {
85
88
  stdio: 'inherit',
86
89
  });
87
90
  return;
88
91
  }
89
92
 
93
+ console.log(`npm install ${packages.join(' ')}`);
90
94
  execSync(`npm install ${packages.join(' ')}`, { stdio: 'inherit' });
91
95
  }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ /**
7
+ * Cases
8
+ * import { A, B } from 'module';
9
+ * import A from 'module';
10
+ * import * as C from 'module';
11
+ * import D, { type E, F } from 'module';
12
+ * import A, { B as B1 } from 'module';
13
+ */
14
+ interface ImportDeclaration {
15
+ // origin statement
16
+ statement: string;
17
+
18
+ // import { A, B } from 'module';
19
+ namedImports?: {
20
+ local?: string;
21
+ imported: string;
22
+ typeOnly?: boolean;
23
+ }[];
24
+
25
+ // import A from 'module';
26
+ defaultImport?: string;
27
+
28
+ // import * as C from 'module';
29
+ namespaceImport?: string;
30
+
31
+ source: string;
32
+ }
33
+
34
+ export function assembleImport(declaration: ImportDeclaration): string {
35
+ const { namedImports, defaultImport, namespaceImport, source } = declaration;
36
+ const importClauses = [];
37
+ if (namedImports) {
38
+ importClauses.push(
39
+ `{ ${namedImports
40
+ .map(
41
+ ({ local, imported, typeOnly }) =>
42
+ `${typeOnly ? 'type ' : ''}${imported}${local ? ` as ${local}` : ''}`
43
+ )
44
+ .join(', ')} }`
45
+ );
46
+ }
47
+ if (defaultImport) {
48
+ importClauses.push(defaultImport);
49
+ }
50
+ if (namespaceImport) {
51
+ importClauses.push(`* as ${namespaceImport}`);
52
+ }
53
+ return `import ${importClauses.join(', ')} from '${source}'`;
54
+ }
55
+
56
+ export function replaceImport(
57
+ fileContent: string,
58
+ origin: ImportDeclaration,
59
+ replaceTo: ImportDeclaration[]
60
+ ): string {
61
+ const replaceImportStatements = replaceTo.map(assembleImport);
62
+ // replace origin statement
63
+ return fileContent.replace(origin.statement, replaceImportStatements.join('\n'));
64
+ }
65
+
66
+ export function* traverseFileImports(fileContent: string): Generator<ImportDeclaration> {
67
+ // 匹配所有 import 语句的正则表达式
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;
70
+
71
+ let match;
72
+ while ((match = importRegex.exec(fileContent)) !== null) {
73
+ const [fullMatch, defaultPart, namespacePart, namedPart, defaultPart2, source] = match;
74
+
75
+ const declaration: ImportDeclaration = {
76
+ statement: fullMatch,
77
+ source: source,
78
+ };
79
+
80
+ // 处理默认导入
81
+ const defaultImport = defaultPart?.trim() || defaultPart2?.trim();
82
+ if (defaultImport && !namespacePart && !namedPart) {
83
+ declaration.defaultImport = defaultImport;
84
+ } else if (defaultImport && (namespacePart || namedPart)) {
85
+ declaration.defaultImport = defaultImport;
86
+ }
87
+
88
+ // 处理命名空间导入 (* as)
89
+ if (namespacePart) {
90
+ declaration.namespaceImport = namespacePart.trim();
91
+ }
92
+
93
+ // 处理命名导入
94
+ if (namedPart) {
95
+ const namedImports = [];
96
+ const namedItems = namedPart
97
+ .split(',')
98
+ .map((item) => item.trim())
99
+ .filter(Boolean);
100
+
101
+ for (const item of namedItems) {
102
+ const typeOnly = item.startsWith('type ');
103
+ const cleanItem = typeOnly ? item.slice(5).trim() : item;
104
+
105
+ if (cleanItem.includes(' as ')) {
106
+ const [imported, local] = cleanItem.split(' as ').map((s) => s.trim());
107
+ namedImports.push({
108
+ imported,
109
+ local,
110
+ typeOnly,
111
+ });
112
+ } else {
113
+ namedImports.push({
114
+ imported: cleanItem,
115
+ typeOnly,
116
+ });
117
+ }
118
+ }
119
+
120
+ if (namedImports.length > 0) {
121
+ declaration.namedImports = namedImports;
122
+ }
123
+ }
124
+
125
+ yield declaration;
126
+ }
127
+ }
@@ -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
+ class File {
10
+ content: string;
11
+
12
+ isUtf8: boolean;
13
+
14
+ relativePath: string;
15
+
16
+ path: string;
17
+
18
+ suffix: string;
19
+
20
+ constructor(filePath: string, public root: string = '/') {
21
+ this.path = filePath;
22
+ this.relativePath = path.relative(this.root, this.path);
23
+ this.suffix = path.extname(this.path);
24
+
25
+ // Check if exists
26
+ if (!fs.existsSync(this.path)) {
27
+ throw Error(`File ${path} Not Exists`);
28
+ }
29
+
30
+ // If no utf-8, skip
31
+ try {
32
+ this.content = fs.readFileSync(this.path, 'utf-8');
33
+ this.isUtf8 = true;
34
+ } catch (e) {
35
+ this.isUtf8 = false;
36
+ return;
37
+ }
38
+ }
39
+
40
+ replace(updater: (content: string) => string) {
41
+ if (!this.isUtf8) {
42
+ console.warn('Not UTF-8 file skipped: ', this.path);
43
+ return;
44
+ }
45
+ this.content = updater(this.content);
46
+ fs.writeFileSync(this.path, this.content, 'utf-8');
47
+ }
48
+ }
49
+
50
+ export function* traverseRecursiveFiles(folder: string): Generator<File> {
51
+ const files = fs.readdirSync(folder);
52
+ for (const file of files) {
53
+ const filePath = path.join(folder, file);
54
+ if (fs.statSync(filePath).isDirectory()) {
55
+ yield* traverseRecursiveFiles(filePath);
56
+ } else {
57
+ yield new File(filePath);
58
+ }
59
+ }
60
+ }