wlmaker 1.7.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
+ };
@@ -7,7 +7,7 @@ import {
7
7
  collaborativeBlocEventTemplate,
8
8
  collaborativeBlocStateTemplate,
9
9
  collaborativeBlocTemplate
10
- } from "./chunk-OTFDN3HG.mjs";
10
+ } from "./chunk-ELYQ3PL5.mjs";
11
11
 
12
12
  // src/generators/collaborative/bloc/generator.ts
13
13
  import * as fs from "fs";
@@ -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
+ };
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/generators/collaborative/collaborative-templates.ts
4
4
  import { camelCase, pascalCase } from "change-case";
5
+ var WORKSPACE_PACKAGES = /* @__PURE__ */ new Set(["app_base", "core", "design_system", "localization"]);
5
6
  var COLLABORATIVE_DEPS = [
6
7
  "app_base",
7
8
  "dio",
@@ -26,7 +27,45 @@ var COLLABORATIVE_OVERRIDE_EXCLUDE = /* @__PURE__ */ new Set([
26
27
  "build_runner",
27
28
  "source_gen"
28
29
  ]);
29
- function collaborativePubspec(featureName, description, versions) {
30
+ function collaborativePubspec(featureName, description, versions, pubWorkspace = false) {
31
+ if (pubWorkspace) {
32
+ const depLines2 = [
33
+ " flutter:",
34
+ " sdk: flutter"
35
+ ];
36
+ for (const dep of COLLABORATIVE_DEPS) {
37
+ depLines2.push(
38
+ WORKSPACE_PACKAGES.has(dep) ? ` ${dep}:` : ` ${dep}: any`
39
+ );
40
+ }
41
+ const devDepLines2 = [
42
+ " flutter_test:",
43
+ " sdk: flutter"
44
+ ];
45
+ for (const dep of COLLABORATIVE_DEV_DEPS) {
46
+ devDepLines2.push(` ${dep}: any`);
47
+ }
48
+ return `name: ${featureName}
49
+ description: "${description}"
50
+ version: 0.0.1
51
+ publish_to: none
52
+
53
+ environment:
54
+ sdk: ^3.8.1
55
+ flutter: ">=1.17.0"
56
+
57
+ resolution: workspace
58
+
59
+ dependencies:
60
+ ${depLines2.join("\n")}
61
+
62
+ dev_dependencies:
63
+ ${devDepLines2.join("\n")}
64
+
65
+ flutter:
66
+ uses-material-design: true
67
+ `;
68
+ }
30
69
  const depLines = [" app_base:"];
31
70
  for (const dep of COLLABORATIVE_DEPS) {
32
71
  if (dep === "app_base") continue;
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  collaborativePageTemplate,
7
7
  collaborativeViewTemplate
8
- } from "./chunk-OTFDN3HG.mjs";
8
+ } from "./chunk-ELYQ3PL5.mjs";
9
9
 
10
10
  // src/generators/collaborative/page/generator.ts
11
11
  import * as fs from "fs";
@@ -39,10 +39,23 @@ function discoverFeatures(projectRoot) {
39
39
  if (!fs.existsSync(featuresDir)) return [];
40
40
  return fs.readdirSync(featuresDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
41
41
  }
42
+ function isMonorepoRoot(dir) {
43
+ if (fs.existsSync(path.join(dir, "melos.yaml"))) {
44
+ return true;
45
+ }
46
+ const pubspecPath = path.join(dir, "pubspec.yaml");
47
+ if (!fs.existsSync(pubspecPath)) return false;
48
+ try {
49
+ const pubspec = YAML.parse(fs.readFileSync(pubspecPath, "utf8"));
50
+ return Boolean(pubspec?.workspace) || Boolean(pubspec?.melos);
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
42
55
  function findMonorepoRoot(startDir) {
43
56
  let dir = startDir;
44
57
  while (dir !== path.dirname(dir)) {
45
- if (fs.existsSync(path.join(dir, "melos.yaml"))) {
58
+ if (isMonorepoRoot(dir)) {
46
59
  return dir;
47
60
  }
48
61
  dir = path.dirname(dir);
@@ -156,6 +169,7 @@ function discoverProjects(searchDir, maxDepth = 2) {
156
169
 
157
170
  export {
158
171
  analyzeProject,
172
+ isMonorepoRoot,
159
173
  findMonorepoRoot,
160
174
  discoverPackages,
161
175
  discoverCollaborativeFeatures,