wlmaker 1.6.0 → 1.8.0

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.
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ blocsModuleTemplate,
4
+ collaborativeGitignore,
5
+ collaborativePubspec,
6
+ datasourcesModuleTemplate,
7
+ emptyBarrelTemplate,
8
+ featureDiTemplate,
9
+ mainBarrelTemplate,
10
+ repositoriesModuleTemplate,
11
+ usecasesModuleTemplate
12
+ } from "./chunk-ELYQ3PL5.mjs";
13
+ import {
14
+ resolveWorkspaceVersions
15
+ } from "./chunk-V4S7DQDN.mjs";
16
+
17
+ // src/generators/collaborative/feature/generator.ts
18
+ import * as fs3 from "fs";
19
+ import * as path3 from "path";
20
+ import { execSync } from "child_process";
21
+ import chalk3 from "chalk";
22
+ import { pascalCase } from "change-case";
23
+
24
+ // src/shared/pub-workspace.ts
25
+ import * as fs from "fs";
26
+ import * as path from "path";
27
+ import YAML from "yaml";
28
+ import chalk from "chalk";
29
+ var WORKSPACE_MEMBER_RE = /^(\s*)-\s+(\S+)\s*$/;
30
+ function usesPubWorkspace(monorepoRoot) {
31
+ const pubspecPath = path.join(monorepoRoot, "pubspec.yaml");
32
+ if (!fs.existsSync(pubspecPath)) return false;
33
+ try {
34
+ const pubspec = YAML.parse(fs.readFileSync(pubspecPath, "utf8"));
35
+ return Array.isArray(pubspec?.workspace);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function addWorkspaceMember(monorepoRoot, memberPath) {
41
+ const pubspecPath = path.join(monorepoRoot, "pubspec.yaml");
42
+ if (!fs.existsSync(pubspecPath)) {
43
+ console.log(chalk.yellow(" No root pubspec.yaml found, skipping workspace registration"));
44
+ return false;
45
+ }
46
+ const content = fs.readFileSync(pubspecPath, "utf8");
47
+ const lines = content.split("\n");
48
+ const workspaceIdx = lines.findIndex((l) => /^workspace:\s*$/.test(l));
49
+ if (workspaceIdx === -1) {
50
+ console.log(
51
+ chalk.yellow(" No workspace: list in root pubspec.yaml (legacy Melos), skipping")
52
+ );
53
+ return false;
54
+ }
55
+ let blockStart = workspaceIdx + 1;
56
+ let blockEnd = blockStart;
57
+ while (blockEnd < lines.length) {
58
+ const line = lines[blockEnd];
59
+ if (line.trim() === "") break;
60
+ if (!WORKSPACE_MEMBER_RE.test(line)) break;
61
+ blockEnd++;
62
+ }
63
+ for (let i = blockStart; i < blockEnd; i++) {
64
+ const match = lines[i].match(WORKSPACE_MEMBER_RE);
65
+ if (match?.[2] === memberPath) {
66
+ console.log(chalk.yellow(` Already in workspace: ${memberPath}`));
67
+ return false;
68
+ }
69
+ }
70
+ let insertAt = blockEnd;
71
+ for (let i = blockStart; i < blockEnd; i++) {
72
+ const match = lines[i].match(WORKSPACE_MEMBER_RE);
73
+ if (match && match[2].localeCompare(memberPath) > 0) {
74
+ insertAt = i;
75
+ break;
76
+ }
77
+ }
78
+ lines.splice(insertAt, 0, ` - ${memberPath}`);
79
+ fs.writeFileSync(pubspecPath, lines.join("\n"));
80
+ console.log(chalk.green(` Added ${memberPath} to root pubspec.yaml workspace`));
81
+ return true;
82
+ }
83
+ function removeMemberPubspecLock(memberDir) {
84
+ const lockPath = path.join(memberDir, "pubspec.lock");
85
+ if (fs.existsSync(lockPath)) {
86
+ fs.unlinkSync(lockPath);
87
+ console.log(chalk.green(" Removed member pubspec.lock (workspace uses root lock)"));
88
+ }
89
+ }
90
+
91
+ // src/shared/ide-workspace.ts
92
+ import * as fs2 from "fs";
93
+ import * as path2 from "path";
94
+ import chalk2 from "chalk";
95
+ function findWorkspaceFile(monorepoRoot) {
96
+ const files = fs2.readdirSync(monorepoRoot);
97
+ return files.find((f) => f.endsWith(".code-workspace")) ?? null;
98
+ }
99
+ function updateCodeWorkspaceFolder(monorepoRoot, folderPath, displayName) {
100
+ const wsFileName = findWorkspaceFile(monorepoRoot);
101
+ if (!wsFileName) {
102
+ console.log(chalk2.yellow(" No .code-workspace file found, skipping"));
103
+ return;
104
+ }
105
+ const wsPath = path2.join(monorepoRoot, wsFileName);
106
+ const content = fs2.readFileSync(wsPath, "utf8");
107
+ if (content.includes(`"path": "${folderPath}"`)) {
108
+ console.log(chalk2.yellow(` Already in ${wsFileName}`));
109
+ return;
110
+ }
111
+ const label = displayName ?? path2.basename(folderPath);
112
+ const folderName = `\u{1F4E6} ${label}`;
113
+ const newBlock = ` {
114
+ "name": "${folderName}",
115
+ "path": "${folderPath}"
116
+ },`;
117
+ const lines = content.split("\n");
118
+ const foldersLineIdx = lines.findIndex((l) => l.includes('"folders"'));
119
+ if (foldersLineIdx === -1) {
120
+ console.log(chalk2.yellow(' No "folders" key found in workspace, skipping'));
121
+ return;
122
+ }
123
+ let foldersOpenBracket = -1;
124
+ for (let j = foldersLineIdx; j < lines.length; j++) {
125
+ if (lines[j].includes("[")) {
126
+ foldersOpenBracket = j;
127
+ break;
128
+ }
129
+ }
130
+ if (foldersOpenBracket === -1) {
131
+ console.log(chalk2.yellow(" Could not find folders array, skipping"));
132
+ return;
133
+ }
134
+ let bracketCount = 0;
135
+ let foldersCloseBracket = -1;
136
+ for (let j = foldersOpenBracket; j < lines.length; j++) {
137
+ for (const ch of lines[j]) {
138
+ if (ch === "[") bracketCount++;
139
+ else if (ch === "]") {
140
+ bracketCount--;
141
+ if (bracketCount === 0) {
142
+ foldersCloseBracket = j;
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ if (foldersCloseBracket !== -1) break;
148
+ }
149
+ if (foldersCloseBracket === -1) {
150
+ console.log(chalk2.yellow(" Could not find folders array end, skipping"));
151
+ return;
152
+ }
153
+ let widgetbookBlockStart = -1;
154
+ let idx = foldersOpenBracket + 1;
155
+ while (idx <= foldersCloseBracket) {
156
+ const trimmed = lines[idx].trim();
157
+ if (trimmed === "{" || trimmed.startsWith("{")) {
158
+ let braceCount = 0;
159
+ let blockEnd = -1;
160
+ for (let j = idx; j <= foldersCloseBracket; j++) {
161
+ for (const ch of lines[j]) {
162
+ if (ch === "{") braceCount++;
163
+ else if (ch === "}") {
164
+ braceCount--;
165
+ if (braceCount === 0) {
166
+ blockEnd = j;
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ if (blockEnd !== -1) break;
172
+ }
173
+ if (blockEnd !== -1) {
174
+ const blockText = lines.slice(idx, blockEnd + 1).join("\n");
175
+ if (blockText.includes("widgetbook")) {
176
+ widgetbookBlockStart = idx;
177
+ break;
178
+ }
179
+ idx = blockEnd + 1;
180
+ continue;
181
+ }
182
+ }
183
+ idx++;
184
+ }
185
+ if (widgetbookBlockStart === -1) {
186
+ console.log(chalk2.yellow(" No widgetbook entry found in workspace, skipping"));
187
+ return;
188
+ }
189
+ lines.splice(widgetbookBlockStart, 0, newBlock);
190
+ fs2.writeFileSync(wsPath, lines.join("\n"));
191
+ console.log(chalk2.green(` Updated ${wsFileName}`));
192
+ }
193
+ function updateHelixWorkspaceFolder(monorepoRoot, folderPath) {
194
+ const helixPath = path2.join(monorepoRoot, ".helix", "languages.toml");
195
+ if (!fs2.existsSync(helixPath)) {
196
+ console.log(chalk2.yellow(" No .helix/languages.toml found, skipping"));
197
+ return;
198
+ }
199
+ const content = fs2.readFileSync(helixPath, "utf8");
200
+ const entry = `{ path = "${folderPath}" }`;
201
+ if (content.includes(entry)) {
202
+ console.log(chalk2.yellow(" Already in .helix/languages.toml"));
203
+ return;
204
+ }
205
+ const workspaceLineRegex = /^\s*\{\s*path\s*=\s*"(?:apps|packages)\//;
206
+ const lines = content.split("\n");
207
+ let lastPackageLineIdx = -1;
208
+ for (let i = 0; i < lines.length; i++) {
209
+ if (workspaceLineRegex.test(lines[i])) {
210
+ lastPackageLineIdx = i;
211
+ }
212
+ }
213
+ const sortKey = folderPath.replace(/^(apps|packages)\//, "");
214
+ if (lastPackageLineIdx === -1) {
215
+ const closingBracketIdx = lines.findIndex((l) => l.trim() === "]");
216
+ if (closingBracketIdx === -1) {
217
+ console.log(chalk2.yellow(" Could not parse .helix/languages.toml"));
218
+ return;
219
+ }
220
+ lines.splice(closingBracketIdx, 0, ` { path = "${folderPath}" },`);
221
+ } else {
222
+ const allEntries = [];
223
+ for (let i = 0; i < lines.length; i++) {
224
+ const m = lines[i].match(/^\s*\{\s*path\s*=\s*"(?:apps|packages)\/([^"]+)"\s*\}/);
225
+ if (m) {
226
+ allEntries.push({ lineIdx: i, entry: m[1] });
227
+ }
228
+ }
229
+ allEntries.push({ lineIdx: -1, entry: sortKey });
230
+ allEntries.sort((a, b) => a.entry.localeCompare(b.entry));
231
+ const insertIdx = allEntries.findIndex((e) => e.entry === sortKey);
232
+ let targetLine;
233
+ if (insertIdx === 0) {
234
+ const firstLine = lines.findIndex((l) => workspaceLineRegex.test(l));
235
+ targetLine = firstLine;
236
+ } else {
237
+ const prevEntry = allEntries[insertIdx - 1];
238
+ targetLine = prevEntry.lineIdx + 1;
239
+ }
240
+ lines.splice(targetLine, 0, ` { path = "${folderPath}" },`);
241
+ }
242
+ fs2.writeFileSync(helixPath, lines.join("\n"));
243
+ console.log(chalk2.green(" Updated .helix/languages.toml"));
244
+ }
245
+
246
+ // src/generators/collaborative/feature/generator.ts
247
+ var SNAKE_CASE_REGEX = /^[a-z][a-z0-9_]*$/;
248
+ async function createCollaborativeFeature(options) {
249
+ const { monorepoRoot, featureName } = options;
250
+ const pascal = pascalCase(featureName);
251
+ const description = options.description ?? `${pascal} collaborative feature`;
252
+ if (!SNAKE_CASE_REGEX.test(featureName)) {
253
+ throw new Error(
254
+ "Feature name must be snake_case (lowercase letters, digits, underscores)."
255
+ );
256
+ }
257
+ const collaborativeDir = path3.join(monorepoRoot, "packages", "collaborative");
258
+ if (!fs3.existsSync(collaborativeDir)) {
259
+ fs3.mkdirSync(collaborativeDir, { recursive: true });
260
+ }
261
+ const pkgDir = path3.join(collaborativeDir, featureName);
262
+ if (fs3.existsSync(pkgDir)) {
263
+ throw new Error(`packages/collaborative/${featureName} already exists`);
264
+ }
265
+ console.log(
266
+ chalk3.bold(`
267
+ Creating collaborative feature: ${chalk3.cyan(featureName)}
268
+ `)
269
+ );
270
+ console.log(chalk3.cyan(" -> Running flutter create..."));
271
+ execSync(
272
+ `flutter create --template=package packages/collaborative/${featureName}`,
273
+ { cwd: monorepoRoot, stdio: "pipe" }
274
+ );
275
+ const autoTestFile = path3.join(pkgDir, "test", `${featureName}_test.dart`);
276
+ if (fs3.existsSync(autoTestFile)) {
277
+ fs3.unlinkSync(autoTestFile);
278
+ }
279
+ removeMemberPubspecLock(pkgDir);
280
+ console.log(chalk3.cyan(" -> Generating feature structure..."));
281
+ const lib = path3.join(pkgDir, "lib");
282
+ const dirs = [
283
+ path3.join(lib, "data", "api", "bff"),
284
+ path3.join(lib, "data", "datasources"),
285
+ path3.join(lib, "data", "models"),
286
+ path3.join(lib, "data", "repositories"),
287
+ path3.join(lib, "domain", "entities"),
288
+ path3.join(lib, "domain", "repositories"),
289
+ path3.join(lib, "domain", "usecases"),
290
+ path3.join(lib, "presentation", "bloc"),
291
+ path3.join(lib, "presentation", "pages", "views"),
292
+ path3.join(lib, "di")
293
+ ];
294
+ for (const dir of dirs) {
295
+ fs3.mkdirSync(dir, { recursive: true });
296
+ }
297
+ const pubWorkspace = usesPubWorkspace(monorepoRoot);
298
+ const workspaceVersions = resolveWorkspaceVersions(monorepoRoot);
299
+ fs3.writeFileSync(
300
+ path3.join(pkgDir, "pubspec.yaml"),
301
+ collaborativePubspec(featureName, description, workspaceVersions, pubWorkspace)
302
+ );
303
+ fs3.writeFileSync(
304
+ path3.join(pkgDir, ".gitignore"),
305
+ collaborativeGitignore()
306
+ );
307
+ const diDir = path3.join(lib, "di");
308
+ fs3.writeFileSync(
309
+ path3.join(diDir, `${featureName}_di.dart`),
310
+ featureDiTemplate(featureName)
311
+ );
312
+ fs3.writeFileSync(
313
+ path3.join(diDir, "blocs_module.dart"),
314
+ blocsModuleTemplate(featureName)
315
+ );
316
+ fs3.writeFileSync(
317
+ path3.join(diDir, "datasources_module.dart"),
318
+ datasourcesModuleTemplate(featureName)
319
+ );
320
+ fs3.writeFileSync(
321
+ path3.join(diDir, "repositories_module.dart"),
322
+ repositoriesModuleTemplate(featureName)
323
+ );
324
+ fs3.writeFileSync(
325
+ path3.join(diDir, "usecases_module.dart"),
326
+ usecasesModuleTemplate(featureName)
327
+ );
328
+ const barrelPaths = [
329
+ path3.join(lib, "data", "api", "bff", "bff.dart"),
330
+ path3.join(lib, "data", "datasources", "datasources.dart"),
331
+ path3.join(lib, "data", "models", "models.dart"),
332
+ path3.join(lib, "data", "repositories", "repositories.dart"),
333
+ path3.join(lib, "domain", "entities", "entities.dart"),
334
+ path3.join(lib, "domain", "repositories", "repositories.dart"),
335
+ path3.join(lib, "domain", "usecases", "usecases.dart"),
336
+ path3.join(lib, "presentation", "bloc", "bloc.dart"),
337
+ path3.join(lib, "presentation", "pages", "views", "views.dart"),
338
+ path3.join(lib, "presentation", "pages", "pages.dart")
339
+ ];
340
+ for (const barrelPath of barrelPaths) {
341
+ fs3.writeFileSync(barrelPath, emptyBarrelTemplate());
342
+ }
343
+ fs3.writeFileSync(
344
+ path3.join(lib, `${featureName}.dart`),
345
+ mainBarrelTemplate(featureName)
346
+ );
347
+ console.log(chalk3.cyan(" -> Updating workspace configuration..."));
348
+ if (pubWorkspace) {
349
+ addWorkspaceMember(monorepoRoot, `packages/collaborative/${featureName}`);
350
+ }
351
+ updateCodeWorkspaceFolder(
352
+ monorepoRoot,
353
+ `packages/collaborative/${featureName}`,
354
+ `collaborative/${featureName}`
355
+ );
356
+ updateHelixWorkspaceFolder(
357
+ monorepoRoot,
358
+ `packages/collaborative/${featureName}`
359
+ );
360
+ if (options.runBootstrap !== false) {
361
+ console.log(chalk3.cyan(" -> Running melos bootstrap..."));
362
+ try {
363
+ execSync("melos bootstrap", { cwd: monorepoRoot, stdio: "pipe" });
364
+ console.log(chalk3.green(" melos bootstrap completed"));
365
+ } catch {
366
+ console.log(
367
+ chalk3.yellow(" melos bootstrap failed (you may need to run it manually)")
368
+ );
369
+ }
370
+ }
371
+ console.log(
372
+ chalk3.green(`
373
+ Feature "${featureName}" created successfully!`)
374
+ );
375
+ console.log(chalk3.gray(` ${pkgDir}`));
376
+ console.log(chalk3.gray(`
377
+ Next steps:`));
378
+ console.log(chalk3.gray(` wlmaker collaborative endpoint ...`));
379
+ console.log(chalk3.gray(` wlmaker collaborative bloc ...`));
380
+ console.log(chalk3.gray(` wlmaker collaborative page ...`));
381
+ }
382
+
383
+ export {
384
+ usesPubWorkspace,
385
+ addWorkspaceMember,
386
+ removeMemberPubspecLock,
387
+ updateCodeWorkspaceFolder,
388
+ updateHelixWorkspaceFolder,
389
+ createCollaborativeFeature
390
+ };
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ injectMethod
4
+ } from "./chunk-7KUDXB2F.mjs";
5
+ import {
6
+ blocRegistrationSnippet,
7
+ collaborativeBlocEventTemplate,
8
+ collaborativeBlocStateTemplate,
9
+ collaborativeBlocTemplate
10
+ } from "./chunk-ELYQ3PL5.mjs";
11
+
12
+ // src/generators/collaborative/bloc/generator.ts
13
+ import * as fs from "fs";
14
+ import * as path from "path";
15
+ import chalk from "chalk";
16
+ import { pascalCase } from "change-case";
17
+ var SNAKE_CASE_REGEX = /^[a-z][a-z0-9_]*$/;
18
+ async function createCollaborativeBloc(options) {
19
+ const { featurePath, blocName } = options;
20
+ const pascal = pascalCase(blocName);
21
+ if (!SNAKE_CASE_REGEX.test(blocName)) {
22
+ throw new Error(
23
+ "BLoC name must be snake_case (lowercase letters, digits, underscores)."
24
+ );
25
+ }
26
+ const lib = path.join(featurePath, "lib");
27
+ const blocDir = path.join(lib, "presentation", "bloc");
28
+ if (!fs.existsSync(blocDir)) {
29
+ throw new Error(
30
+ `Not a collaborative feature structure. Missing: ${blocDir}`
31
+ );
32
+ }
33
+ const blocFile = path.join(blocDir, `${blocName}_bloc.dart`);
34
+ if (fs.existsSync(blocFile)) {
35
+ throw new Error(`BLoC "${blocName}" already exists at ${blocFile}`);
36
+ }
37
+ fs.writeFileSync(
38
+ blocFile,
39
+ collaborativeBlocTemplate(blocName, pascal)
40
+ );
41
+ console.log(chalk.green(` BLoC created: ${blocFile}`));
42
+ fs.writeFileSync(
43
+ path.join(blocDir, `${blocName}_event.dart`),
44
+ collaborativeBlocEventTemplate(blocName, pascal)
45
+ );
46
+ fs.writeFileSync(
47
+ path.join(blocDir, `${blocName}_state.dart`),
48
+ collaborativeBlocStateTemplate(blocName, pascal)
49
+ );
50
+ console.log(chalk.green(` Event + State files created`));
51
+ const barrelPath = path.join(blocDir, "bloc.dart");
52
+ const exportLine = `export '${blocName}_bloc.dart';`;
53
+ if (fs.existsSync(barrelPath)) {
54
+ const content = fs.readFileSync(barrelPath, "utf8");
55
+ if (!content.includes(exportLine)) {
56
+ fs.writeFileSync(barrelPath, content.trimEnd() + "\n" + exportLine + "\n");
57
+ }
58
+ } else {
59
+ fs.writeFileSync(barrelPath, exportLine + "\n");
60
+ }
61
+ const featureName = path.basename(featurePath);
62
+ const diDir = path.join(lib, "di");
63
+ const blocsModulePath = path.join(diDir, "blocs_module.dart");
64
+ if (fs.existsSync(blocsModulePath)) {
65
+ const snippet = blocRegistrationSnippet(blocName, pascal);
66
+ const importLine = `import 'package:${featureName}/presentation/bloc/${blocName}_bloc.dart';`;
67
+ const content = fs.readFileSync(blocsModulePath, "utf8");
68
+ if (!content.includes(importLine)) {
69
+ const importRegex = /^import\s+[^;]+;/gm;
70
+ const imports = [...content.matchAll(importRegex)];
71
+ if (imports.length > 0) {
72
+ const lastImport = imports[imports.length - 1];
73
+ const insertPos = lastImport.index + lastImport[0].length;
74
+ const newContent = content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos);
75
+ fs.writeFileSync(blocsModulePath, newContent);
76
+ }
77
+ }
78
+ injectMethod(blocsModulePath, "BlocsModule", snippet);
79
+ console.log(chalk.green(" Registered in BlocsModule"));
80
+ }
81
+ }
82
+
83
+ export {
84
+ createCollaborativeBloc
85
+ };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/analyzer/region-parser.ts
4
+ function parseRegions(code) {
5
+ const regions = {};
6
+ const errors = [];
7
+ const openStack = [];
8
+ const lines = code.split("\n");
9
+ const regionStart = /\/\/\s*#?\s*region\s+(.+)/i;
10
+ const regionEnd = /\/\/\s*#?\s*endregion/i;
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const line = lines[i];
13
+ const lineNum = i + 1;
14
+ const startMatch = line.match(regionStart);
15
+ if (startMatch) {
16
+ const name = startMatch[1].trim();
17
+ if (openStack.length > 0) {
18
+ errors.push(
19
+ `Line ${lineNum}: nested region "${name}" inside "${openStack[openStack.length - 1].name}" \u2014 regions cannot be nested`
20
+ );
21
+ continue;
22
+ }
23
+ openStack.push({ name, startLine: lineNum });
24
+ continue;
25
+ }
26
+ const endMatch = line.match(regionEnd);
27
+ if (endMatch) {
28
+ if (openStack.length === 0) {
29
+ errors.push(`Line ${lineNum}: //#endregion without matching //#region`);
30
+ continue;
31
+ }
32
+ const opened = openStack.pop();
33
+ regions[opened.name] = {
34
+ name: opened.name,
35
+ content: lines.slice(opened.startLine, i).join("\n").trim(),
36
+ startLine: opened.startLine,
37
+ endLine: lineNum
38
+ };
39
+ }
40
+ }
41
+ for (const opened of openStack) {
42
+ errors.push(`Line ${opened.startLine}: unclosed region "${opened.name}" \u2014 missing //#endregion`);
43
+ }
44
+ return { regions, errors };
45
+ }
46
+
47
+ export {
48
+ parseRegions
49
+ };
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/dart/injector.ts
4
+ import * as fs from "fs";
5
+ function injectMethod(filePath, className, methodCode, dedupKey) {
6
+ const content = fs.readFileSync(filePath, "utf8");
7
+ const dedup = dedupKey ?? findSignature(methodCode);
8
+ if (dedup && content.includes(dedup)) {
9
+ return;
10
+ }
11
+ const classRegex = new RegExp(`class\\s+${className}\\s*[^{]*\\{`);
12
+ const classMatch = content.match(classRegex);
13
+ if (!classMatch) {
14
+ throw new Error(`Class "${className}" not found in ${filePath}`);
15
+ }
16
+ const classStart = content.indexOf(classMatch[0]);
17
+ let braceCount = 0;
18
+ let classEnd = -1;
19
+ let foundOpen = false;
20
+ for (let i = classStart; i < content.length; i++) {
21
+ if (content[i] === "{") {
22
+ braceCount++;
23
+ foundOpen = true;
24
+ } else if (content[i] === "}") {
25
+ braceCount--;
26
+ if (foundOpen && braceCount === 0) {
27
+ classEnd = i;
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ if (classEnd === -1) {
33
+ throw new Error(`Could not find closing brace for class "${className}" in ${filePath}`);
34
+ }
35
+ const indentedMethod = methodCode.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
36
+ const newContent = content.slice(0, classEnd) + "\n" + indentedMethod + "\n" + content.slice(classEnd);
37
+ fs.writeFileSync(filePath, newContent);
38
+ }
39
+ function injectImport(filePath, importLine) {
40
+ const content = fs.readFileSync(filePath, "utf8");
41
+ if (content.includes(importLine.trim())) {
42
+ return;
43
+ }
44
+ const importRegex = /^import\s+[^;]+;/gm;
45
+ const imports = [...content.matchAll(importRegex)];
46
+ if (imports.length > 0) {
47
+ const lastImport = imports[imports.length - 1];
48
+ const insertPos = lastImport.index + lastImport[0].length;
49
+ const newContent = content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos);
50
+ fs.writeFileSync(filePath, newContent);
51
+ } else {
52
+ fs.writeFileSync(filePath, importLine + "\n\n" + content);
53
+ }
54
+ }
55
+ function injectExport(filePath, exportLine) {
56
+ let content = "";
57
+ if (fs.existsSync(filePath)) {
58
+ content = fs.readFileSync(filePath, "utf8");
59
+ }
60
+ if (content.includes(exportLine)) {
61
+ return;
62
+ }
63
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
64
+ lines.push(exportLine);
65
+ lines.sort();
66
+ fs.writeFileSync(filePath, lines.join("\n") + "\n");
67
+ }
68
+ function findSignature(methodCode) {
69
+ const lines = methodCode.trim().split("\n");
70
+ for (const line of lines) {
71
+ const stripped = line.trim();
72
+ if (stripped && !stripped.startsWith("@")) {
73
+ return stripped;
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ export {
80
+ injectMethod,
81
+ injectImport,
82
+ injectExport
83
+ };