@akanjs/devkit 2.3.11-rc.4 → 2.3.11-rc.6

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/executors.test.ts CHANGED
@@ -140,7 +140,7 @@ describe("Executor filesystem helpers", () => {
140
140
  expect(await readFile(path.join(root, "workspace/.env"), "utf8")).toContain("AKAN_PUBLIC_REPO_NAME");
141
141
  expect(await readFile(path.join(root, "workspace/.vscode/settings.json"), "utf8")).toContain("typescript.tsdk");
142
142
  expect(await readFile(path.join(root, "workspace/.cursor/rules/akan.mdc"), "utf8")).toContain(
143
- "Akan.js Workspace Rules",
143
+ "Akan workspace agent guide",
144
144
  );
145
145
  expect(await readFile(path.join(root, "workspace/AGENTS.md"), "utf8")).toContain("sample Agent Guide");
146
146
  expect(await readFile(path.join(root, "workspace/docs/AI-DEVELOPMENT.md"), "utf8")).toContain(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.3.11-rc.4",
3
+ "version": "2.3.11-rc.6",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@tailwindcss/node": "^4.3.0",
34
34
  "@trapezedev/project": "^7.1.4",
35
- "akanjs": "2.3.11-rc.4",
35
+ "akanjs": "2.3.11-rc.6",
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
38
  "daisyui": "5.5.23",
package/qualityScanner.ts CHANGED
@@ -37,6 +37,7 @@ interface ExportedFunctionLike {
37
37
  file: string;
38
38
  line: number;
39
39
  bodyFingerprint?: string;
40
+ duplicateNameExempt: boolean;
40
41
  }
41
42
 
42
43
  interface TopLevelDeclaration {
@@ -177,7 +178,8 @@ export class AkanQualityScanner {
177
178
  const exportedFunctionLikes = sourceFiles.flatMap((sourceFile) => getExportedFunctionLikes(sourceFile));
178
179
  const warnings: QualityWarning[] = [];
179
180
 
180
- for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
181
+ const nameCheckedDeclarations = exportedFunctionLikes.filter((declaration) => !declaration.duplicateNameExempt);
182
+ for (const [name, declarations] of groupBy(nameCheckedDeclarations, (declaration) => declaration.name)) {
181
183
  if (declarations.length < 2) continue;
182
184
  warnings.push({
183
185
  rule: "akan.global.duplicate-exported-function-name",
@@ -345,6 +347,7 @@ function formatQualityLocation(file: string | undefined, line: number | undefine
345
347
 
346
348
  function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionLike[] {
347
349
  const declarations: ExportedFunctionLike[] = [];
350
+ const pageExempt = isPageRouteFile(sourceFile.file);
348
351
  for (const statement of sourceFile.sourceFile.statements) {
349
352
  if (ts.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
350
353
  declarations.push({
@@ -353,6 +356,7 @@ function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionL
353
356
  file: sourceFile.file,
354
357
  line: getLine(sourceFile.sourceFile, statement),
355
358
  bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement.body),
359
+ duplicateNameExempt: pageExempt || isConventionDuplicateNameExempt(sourceFile.file, false),
356
360
  });
357
361
  }
358
362
  if (ts.isClassDeclaration(statement) && statement.name && isExported(statement)) {
@@ -362,6 +366,9 @@ function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionL
362
366
  file: sourceFile.file,
363
367
  line: getLine(sourceFile.sourceFile, statement),
364
368
  bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement),
369
+ duplicateNameExempt:
370
+ pageExempt ||
371
+ isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement)),
365
372
  });
366
373
  }
367
374
  if (ts.isVariableStatement(statement) && isExported(statement)) {
@@ -373,6 +380,7 @@ function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionL
373
380
  file: sourceFile.file,
374
381
  line: getLine(sourceFile.sourceFile, declaration),
375
382
  bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, declaration.initializer),
383
+ duplicateNameExempt: pageExempt || isConventionDuplicateNameExempt(sourceFile.file, false),
376
384
  });
377
385
  }
378
386
  }
@@ -380,6 +388,38 @@ function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionL
380
388
  return declarations;
381
389
  }
382
390
 
391
+ function isPageRouteFile(file: string) {
392
+ const segments = file.split("/");
393
+ return (segments[0] === "apps" || segments[0] === "libs") && segments[2] === "page";
394
+ }
395
+
396
+ function isConventionDuplicateNameExempt(file: string, isEnumClass: boolean) {
397
+ if (!isInLibModule(file)) return false;
398
+ if (file.endsWith(".tsx")) return true;
399
+ if (
400
+ file.endsWith(".document.ts") ||
401
+ file.endsWith(".service.ts") ||
402
+ file.endsWith(".signal.ts") ||
403
+ file.endsWith(".store.ts")
404
+ )
405
+ return true;
406
+ // Model view classes may repeat across modules; enum classes must stay uniquely named.
407
+ if (file.endsWith(".constant.ts")) return !isEnumClass;
408
+ return false;
409
+ }
410
+
411
+ function isInLibModule(file: string) {
412
+ const segments = file.split("/");
413
+ return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
414
+ }
415
+
416
+ function isEnumClassStatement(sourceFile: ts.SourceFile, statement: ts.Statement) {
417
+ if (!ts.isClassDeclaration(statement)) return false;
418
+ const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
419
+ const expression = heritageClause?.types[0]?.expression;
420
+ return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
421
+ }
422
+
383
423
  function getExportedClassNames(sourceFile: ts.SourceFile) {
384
424
  return sourceFile.statements
385
425
  .filter((statement): statement is ts.ClassDeclaration => ts.isClassDeclaration(statement) && !!statement.name)
@@ -403,11 +443,7 @@ function getPlaceholderExportWarnings(sourceFile: SourceFileInfo): QualityWarnin
403
443
 
404
444
  function getDictionaryTextWarnings(sourceFile: SourceFileInfo): QualityWarning[] {
405
445
  if (!sourceFile.file.endsWith(".dictionary.ts")) return [];
406
- const stalePatterns = [
407
- { pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
408
- { pattern: /settting/, label: "misspelling: settting" },
409
- { pattern: /배너 수/, label: "stale copied Korean domain noun: 배너 수" },
410
- ];
446
+ const stalePatterns = [{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" }];
411
447
  return stalePatterns.flatMap(({ pattern, label }) =>
412
448
  findPatternLines(sourceFile.content, pattern).map((line) => ({
413
449
  rule: "akan.file.dictionary-stale-text",
package/scanInfo.ts CHANGED
@@ -52,6 +52,7 @@ const appRootAllowedFiles = new Set([
52
52
  "package.json",
53
53
  "server.ts",
54
54
  "tsconfig.json",
55
+ "tsconfig.tsbuildinfo",
55
56
  ]);
56
57
  const appRootAllowedDirs = new Set([
57
58
  ".akan",