@akanjs/devkit 3.0.0-alpha.51 → 3.0.0-alpha.53

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @akanjs/devkit
2
2
 
3
+ ## 2.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6d58c7e]
8
+ - Updated dependencies [11aa655]
9
+ - Updated dependencies [25d5b15]
10
+ - Updated dependencies [11aa655]
11
+ - Updated dependencies [11aa655]
12
+ - Updated dependencies [25d5b15]
13
+ - Updated dependencies [42cf7a2]
14
+ - Updated dependencies [25d5b15]
15
+ - Updated dependencies [04cb46d]
16
+ - akanjs@2.4.2
17
+
3
18
  ## 2.4.1
4
19
 
5
20
  ### Patch Changes
@@ -55,7 +55,7 @@ interface RootBoundary {
55
55
  segments: string[];
56
56
  }
57
57
 
58
- function getRootBoundarySegments(key: string): string[] | null {
58
+ export function getRootBoundarySegments(key: string): string[] | null {
59
59
  const match = LAYOUT_KEY_RE.exec(key);
60
60
  if (!match) return null;
61
61
  const prefix = match[1]?.replace(/\/$/, "");
@@ -76,7 +76,7 @@ function implicitDictionaryMacroAbsPath(appCwdPath: string): string {
76
76
  return path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
77
77
  }
78
78
 
79
- function isRootBoundarySegments(segments: string[], basePaths: Iterable<string>): boolean {
79
+ export function isRootBoundarySegments(segments: string[], basePaths: Iterable<string>): boolean {
80
80
  const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
81
81
  if (firstVisibleIndex === -1) return segments.length <= 1;
82
82
  if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment))) return false;
package/executors.test.ts CHANGED
@@ -621,6 +621,64 @@ describe("Workspace and app executor environment contracts", () => {
621
621
  });
622
622
  });
623
623
 
624
+ describe("root layout source validation during page key discovery", () => {
625
+ const makeRouteValidationApp = async (appName: string, config: string, files: Record<string, string>) => {
626
+ const root = await makeTempRoot();
627
+ process.env.AKAN_PUBLIC_REPO_NAME = "repo";
628
+ process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.com";
629
+ process.env.AKAN_PUBLIC_ENV = "local";
630
+ await writeJson(path.join(root, "package.json"), rootPackageJson());
631
+ await mkdir(path.join(root, "apps", appName), { recursive: true });
632
+ await writeFile(path.join(root, `apps/${appName}/akan.config.ts`), config);
633
+ for (const [file, source] of Object.entries(files)) {
634
+ const filePath = path.join(root, `apps/${appName}/page`, file);
635
+ await mkdir(path.dirname(filePath), { recursive: true });
636
+ await writeFile(filePath, source);
637
+ }
638
+ const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
639
+ return AppExecutor.from(workspace, appName);
640
+ };
641
+ const wsConnectLayout = [
642
+ "export const wsConnect = false;",
643
+ "export default function Layout({ children }) { return children; }",
644
+ "",
645
+ ].join("\n");
646
+
647
+ test("accepts wsConnect on a configured base-path root layout", async () => {
648
+ process.env.AKAN_PUBLIC_BASE_PATHS = "web,admin";
649
+ const app = await makeRouteValidationApp(
650
+ "base-path-root-ws",
651
+ [
652
+ "export default {",
653
+ ' routes: [{ basePath: "web", domains: {} }, { basePath: "admin", domains: {} }],',
654
+ "};",
655
+ "",
656
+ ].join("\n"),
657
+ { "admin/_layout.tsx": wsConnectLayout },
658
+ );
659
+
660
+ await expect(app.getPageKeys({ refresh: true })).resolves.toEqual(["./admin/_layout.tsx"]);
661
+ });
662
+
663
+ test("rejects wsConnect on a nested layout", async () => {
664
+ const app = await makeRouteValidationApp(
665
+ "nested-layout-ws",
666
+ 'export default { routes: [{ basePath: "admin", domains: {} }] };\n',
667
+ { "admin/users/_layout.tsx": wsConnectLayout },
668
+ );
669
+
670
+ await expect(app.getPageKeys({ refresh: true })).rejects.toThrow(/unsupported export "wsConnect"/);
671
+ });
672
+
673
+ test("accepts wsConnect on a grouped root layout", async () => {
674
+ const app = await makeRouteValidationApp("grouped-root-ws", "export default {};\n", {
675
+ "(docs)/_layout.tsx": wsConnectLayout,
676
+ });
677
+
678
+ await expect(app.getPageKeys({ refresh: true })).resolves.toEqual(["./(docs)/_layout.tsx"]);
679
+ });
680
+ });
681
+
624
682
  test("accepts metadata route exports during page key discovery", async () => {
625
683
  const root = await makeTempRoot();
626
684
  process.env.AKAN_PUBLIC_REPO_NAME = "repo";
package/executors.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  upsertAgentBlock,
41
41
  } from "./agentsIndex";
42
42
  import { AkanAppConfig, AkanLibConfig, decreaseBuildNum, increaseBuildNum } from "./akanConfig";
43
+ import { getRootBoundarySegments, isRootBoundarySegments } from "./artifact/implicitRootLayout";
43
44
  import { FileSys } from "./fileSys";
44
45
  import { getDirname } from "./getDirname";
45
46
  import { Linter } from "./linter";
@@ -1469,7 +1470,8 @@ export class AppExecutor extends SysExecutor {
1469
1470
  );
1470
1471
  }
1471
1472
  if (!owner) owners.set(routeId, { absPath, fromLib });
1472
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
1473
+ const layoutSegments = getRootBoundarySegments(key);
1474
+ const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
1473
1475
  const routeSource = await Bun.file(absPath).text();
1474
1476
  const validator = await AppExecutor.#getRouteSourceValidator();
1475
1477
  if (parsed.kind === "overrides") validator.validateOverridesSourceExports(routeSource, absPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.0.0-alpha.51",
3
+ "version": "3.0.0-alpha.53",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -45,7 +45,7 @@
45
45
  "@langchain/openai": "^1.4.6",
46
46
  "@tailwindcss/node": "^4.3.0",
47
47
  "@trapezedev/project": "^7.1.4",
48
- "akanjs": "3.0.0-alpha.51",
48
+ "akanjs": "3.0.0-alpha.53",
49
49
  "chalk": "^5.6.2",
50
50
  "commander": "^14.0.3",
51
51
  "dayjs": "^1.11.20",