@akanjs/devkit 3.0.0-alpha.84 → 3.0.0-alpha.86

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/biome.base.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.12/schema.json",
3
3
  "vcs": {
4
4
  "enabled": true,
5
5
  "clientKind": "git",
@@ -24,7 +24,9 @@
24
24
  },
25
25
  "noArrayIndexKey": "off",
26
26
  "noShadowRestrictedNames": "off",
27
- "noUnnecessaryConditions": "off"
27
+ "noUnnecessaryConditions": {
28
+ "level": "warn"
29
+ }
28
30
  },
29
31
  "correctness": {
30
32
  "noUnusedFunctionParameters": "off",
@@ -58,10 +60,10 @@
58
60
  }
59
61
  },
60
62
  "domains": {
63
+ "project": "recommended",
61
64
  "react": "recommended",
62
65
  "test": "recommended",
63
- "types": "none",
64
- "project": "none"
66
+ "types": "all"
65
67
  }
66
68
  },
67
69
  "javascript": {
package/biomeBase.ts CHANGED
@@ -6,4 +6,4 @@ export const biomeBaseConfig = "@akanjs/devkit/biome.base.json";
6
6
  // Biome disagrees with the shipped base config therefore fails to load it at all, which is why the version is
7
7
  // pinned here instead of resolved to latest at create time. Bump this and `biome.base.json` in one commit, and run
8
8
  // `biome migrate --write` in the workspace root and in `pkgs/@akanjs/devkit` so both configs move together.
9
- export const biomeVersion = "2.5.8";
9
+ export const biomeVersion = "2.5.12";
@@ -377,7 +377,7 @@ export class SourceMtimeIndex {
377
377
  return !name || name.startsWith(".") || name === "node_modules";
378
378
  }
379
379
 
380
- /** `WatchRootResolver` can return `apps/<app>/page` alongside `apps/`; walking both doubles the work. */
380
+ /** `WatchRootResolver` can return `apps/<app>/page` alongside `apps/<app>`; walking both doubles the work. */
381
381
  static #pruneNestedRoots(roots: string[]): string[] {
382
382
  const resolved = [...new Set(roots.map((root) => path.resolve(root)))].sort();
383
383
  return resolved.filter(
@@ -0,0 +1,67 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import type { App } from "../commandDecorators";
6
+ import { WatchRootResolver } from "./watchRootResolver";
7
+
8
+ const roots: string[] = [];
9
+
10
+ const makeWorkspace = async (dirs: string[]) => {
11
+ const root = await mkdtemp(path.join(os.tmpdir(), "akan-watch-roots-"));
12
+ roots.push(root);
13
+ await Promise.all(dirs.map((dir) => mkdir(path.join(root, dir), { recursive: true })));
14
+ return root;
15
+ };
16
+
17
+ const makeApp = (workspaceRoot: string, name: string, paths: Record<string, string[]>) =>
18
+ ({
19
+ cwdPath: path.join(workspaceRoot, "apps", name),
20
+ workspace: { workspaceRoot },
21
+ getTsConfig: async () => ({ compilerOptions: { paths } }),
22
+ }) as unknown as App;
23
+
24
+ afterEach(async () => {
25
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
26
+ });
27
+
28
+ describe("WatchRootResolver", () => {
29
+ test("narrows the apps container to the app being served", async () => {
30
+ const workspaceRoot = await makeWorkspace(["apps/app1/page", "apps/app2/page", "libs/util"]);
31
+ const resolved = await new WatchRootResolver(
32
+ makeApp(workspaceRoot, "app1", { "@apps/*": ["./apps/*"], "@libs/*": ["./libs/*"] }),
33
+ ).resolve();
34
+
35
+ expect(resolved).toContain(path.join(workspaceRoot, "apps/app1"));
36
+ expect(resolved).not.toContain(path.join(workspaceRoot, "apps"));
37
+ expect(resolved).not.toContain(path.join(workspaceRoot, "apps/app2"));
38
+ });
39
+
40
+ test("keeps the libs container whole", async () => {
41
+ const workspaceRoot = await makeWorkspace(["apps/app1/page", "libs/util", "libs/shared"]);
42
+ const resolved = await new WatchRootResolver(makeApp(workspaceRoot, "app1", { "@libs/*": ["./libs/*"] })).resolve();
43
+
44
+ expect(resolved).toContain(path.join(workspaceRoot, "libs"));
45
+ });
46
+
47
+ test("resolves a package alias to its own directory, filename and glob stripped", async () => {
48
+ const workspaceRoot = await makeWorkspace(["apps/app1/page", "pkgs/akanjs/base"]);
49
+ const resolved = await new WatchRootResolver(
50
+ makeApp(workspaceRoot, "app1", {
51
+ akanjs: ["./pkgs/akanjs/index.ts"],
52
+ "akanjs/*": ["./pkgs/akanjs/*"],
53
+ missing: ["./pkgs/nothing/index.ts"],
54
+ }),
55
+ ).resolve();
56
+
57
+ expect(resolved).toContain(path.join(workspaceRoot, "pkgs/akanjs"));
58
+ expect(resolved).not.toContain(path.join(workspaceRoot, "pkgs/nothing"));
59
+ });
60
+
61
+ test("always watches the app's page tree", async () => {
62
+ const workspaceRoot = await makeWorkspace(["apps/app1/page"]);
63
+ const resolved = await new WatchRootResolver(makeApp(workspaceRoot, "app1", {})).resolve();
64
+
65
+ expect(resolved).toEqual([path.join(workspaceRoot, "apps/app1/page")]);
66
+ });
67
+ });
@@ -2,6 +2,15 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import type { App } from "../commandDecorators";
4
4
 
5
+ /**
6
+ * The directories one app's dev watcher follows, resolved from its tsconfig `paths`.
7
+ *
8
+ * `@apps/*` strips down to the `apps/` container, so taking it verbatim puts every sibling app under the
9
+ * watcher: with two dev servers up, a save in app2 rebuilds, restarts and reloads app1, and both builders
10
+ * rewrite the same generated barrels. Apps are leaves of the workspace graph — never one another's
11
+ * dependencies — so the container is replaced by this app's own directory. `libs/` stays whole, because a
12
+ * lib becomes a dependency the moment someone types the import, with no restart in between.
13
+ */
5
14
  export class WatchRootResolver {
6
15
  #app: App;
7
16
 
@@ -11,6 +20,8 @@ export class WatchRootResolver {
11
20
 
12
21
  async resolve(): Promise<string[]> {
13
22
  const tsconfig = await this.#app.getTsConfig();
23
+ const appDir = path.resolve(this.#app.cwdPath);
24
+ const appsContainer = path.dirname(appDir);
14
25
  const set = new Set<string>();
15
26
  set.add(path.resolve(`${this.#app.cwdPath}/page`));
16
27
  for (const targets of Object.values(tsconfig.compilerOptions.paths ?? {})) {
@@ -20,7 +31,8 @@ export class WatchRootResolver {
20
31
  // Strip the trailing filename and glob so we watch the package root dir.
21
32
  const cleaned = target.replace(/\/?\*+.*$/, "").replace(/\/[^/]+\.[^/]+$/, "");
22
33
  const resolved = path.resolve(this.#app.workspace.workspaceRoot, cleaned);
23
- if (fs.existsSync(resolved)) set.add(resolved);
34
+ const root = resolved === appsContainer ? appDir : resolved;
35
+ if (fs.existsSync(root)) set.add(root);
24
36
  }
25
37
  }
26
38
  return [...set];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.0.0-alpha.84",
3
+ "version": "3.0.0-alpha.86",
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.84",
48
+ "akanjs": "3.0.0-alpha.86",
49
49
  "chalk": "^5.6.2",
50
50
  "commander": "^14.0.3",
51
51
  "dayjs": "^1.11.20",