@akanjs/devkit 2.1.0-rc.9 → 2.1.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.
@@ -132,6 +132,7 @@ describe("AkanAppConfig", () => {
132
132
  react: "19.0.0",
133
133
  "react-dom": "19.0.0",
134
134
  "react-server-dom-webpack": "19.0.0",
135
+ croner: akanPackageJson.peerDependencies?.croner,
135
136
  sharp: "1.0.0",
136
137
  "@external/runtime": "2.0.0",
137
138
  },
@@ -168,10 +169,45 @@ describe("AkanAppConfig", () => {
168
169
  react: runtimeDependencies.react,
169
170
  "react-dom": runtimeDependencies["react-dom"],
170
171
  "react-server-dom-webpack": runtimeDependencies["react-server-dom-webpack"],
172
+ croner: runtimeDependencies.croner,
171
173
  sharp: runtimeDependencies.sharp,
172
174
  });
173
175
  });
174
176
 
177
+ test("adds backend runtime packages by database mode", () => {
178
+ const runtimeDependencies = { ...akanPackageJson.dependencies, ...akanPackageJson.peerDependencies };
179
+ const singleConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "single" }, baseDevEnv);
180
+ const multipleConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "multiple" }, baseDevEnv);
181
+ const clusterConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "cluster" }, baseDevEnv);
182
+
183
+ expect(singleConfig.getProductionPackageJson().dependencies).toMatchObject({
184
+ croner: runtimeDependencies.croner,
185
+ });
186
+ expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("ioredis");
187
+ expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("bullmq");
188
+ expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("@libsql/client");
189
+ expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("postgres");
190
+ expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("protobufjs");
191
+
192
+ expect(multipleConfig.getProductionPackageJson().dependencies).toMatchObject({
193
+ "@libsql/client": runtimeDependencies["@libsql/client"],
194
+ bullmq: runtimeDependencies.bullmq,
195
+ croner: runtimeDependencies.croner,
196
+ ioredis: runtimeDependencies.ioredis,
197
+ protobufjs: runtimeDependencies.protobufjs,
198
+ });
199
+ expect(multipleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("postgres");
200
+
201
+ expect(clusterConfig.getProductionPackageJson().dependencies).toMatchObject({
202
+ bullmq: runtimeDependencies.bullmq,
203
+ croner: runtimeDependencies.croner,
204
+ ioredis: runtimeDependencies.ioredis,
205
+ postgres: runtimeDependencies.postgres,
206
+ protobufjs: runtimeDependencies.protobufjs,
207
+ });
208
+ expect(clusterConfig.getProductionPackageJson().dependencies).not.toHaveProperty("@libsql/client");
209
+ });
210
+
175
211
  test("normalizes multiple mobile targets and validates base paths", () => {
176
212
  const config = new AkanAppConfig(
177
213
  app,
@@ -50,7 +50,18 @@ const DEFAULT_OPTIMIZE_IMPORTS = [
50
50
  const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
51
51
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
52
52
  const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
53
- const AKAN_RUNTIME_PACKAGES = new Set<string>([...SSR_RUNTIME_PACKAGES, ...NATIVE_RUNTIME_PACKAGES]);
53
+ const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
54
+ const DATABASE_MODE_RUNTIME_PACKAGES = {
55
+ single: [],
56
+ multiple: ["@libsql/client", "bullmq", "ioredis", "protobufjs"],
57
+ cluster: ["bullmq", "ioredis", "postgres", "protobufjs"],
58
+ } satisfies Record<DatabaseMode, readonly string[]>;
59
+ const AKAN_RUNTIME_PACKAGES = new Set<string>([
60
+ ...SSR_RUNTIME_PACKAGES,
61
+ ...NATIVE_RUNTIME_PACKAGES,
62
+ ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
63
+ ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
64
+ ]);
54
65
  const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
55
66
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
56
67
  imageSizes: [32, 48, 64, 96, 128, 256, 384],
@@ -272,6 +283,15 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
272
283
  if (AKAN_RUNTIME_PACKAGES.has(lib))
273
284
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
274
285
  }
286
+ #getProductionRuntimePackages() {
287
+ return [
288
+ ...this.externalLibs,
289
+ ...SSR_RUNTIME_PACKAGES,
290
+ ...NATIVE_RUNTIME_PACKAGES,
291
+ ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
292
+ ...DATABASE_MODE_RUNTIME_PACKAGES[this.defaultDatabaseMode],
293
+ ];
294
+ }
275
295
  getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
276
296
  return {
277
297
  name: this.app.name,
@@ -279,7 +299,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
279
299
  version: "1.0.0",
280
300
  main: "./main.js",
281
301
  dependencies: Object.fromEntries(
282
- [...new Set([...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...NATIVE_RUNTIME_PACKAGES])].map((lib) => {
302
+ [...new Set(this.#getProductionRuntimePackages())].map((lib) => {
283
303
  const version = this.#resolveProductionDependencyVersion(lib);
284
304
  if (!version) throw new Error(`Dependency ${lib} not found in package.json`);
285
305
  return [lib, version];
@@ -0,0 +1,10 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { AKAN_OPTIONAL_BACKEND_EXTERNALS } from "./applicationBuildRunner";
3
+
4
+ describe("ApplicationBuildRunner", () => {
5
+ test("externalizes Akan optional backend dependencies", () => {
6
+ expect(AKAN_OPTIONAL_BACKEND_EXTERNALS).toEqual(
7
+ expect.arrayContaining(["@libsql/client", "bullmq", "croner", "ioredis", "postgres", "protobufjs"]),
8
+ );
9
+ });
10
+ });
@@ -47,10 +47,21 @@ const SSR_RENDER_EXTERNALS = [
47
47
  "react/jsx-dev-runtime",
48
48
  "react-dom",
49
49
  "react-dom/server.browser",
50
+ "react-server-dom-webpack",
51
+ "react-server-dom-webpack/server.node",
50
52
  "react-server-dom-webpack/client.node",
51
53
  "react-server-dom-webpack/client.browser",
52
54
  ] as const;
53
55
 
56
+ export const AKAN_OPTIONAL_BACKEND_EXTERNALS = [
57
+ "@libsql/client",
58
+ "bullmq",
59
+ "croner",
60
+ "ioredis",
61
+ "postgres",
62
+ "protobufjs",
63
+ ] as const;
64
+
54
65
  export class ApplicationBuildRunner {
55
66
  #app: App;
56
67
  #fast: boolean;
@@ -161,7 +172,9 @@ export class ApplicationBuildRunner {
161
172
 
162
173
  async #buildBackend() {
163
174
  const akanConfig = await this.#app.getConfig();
164
- const backendExternals = [...new Set([...akanConfig.externalLibs, ...SSR_RENDER_EXTERNALS])];
175
+ const backendExternals = [
176
+ ...new Set([...akanConfig.externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]),
177
+ ];
165
178
  const backendEntryPoints = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
166
179
  for (const entrypoint of backendEntryPoints) {
167
180
  if (!(await Bun.file(entrypoint).exists())) throw new Error(`Backend entrypoint not found: ${entrypoint}`);
@@ -183,8 +196,7 @@ export class ApplicationBuildRunner {
183
196
  conditions: ["react-server"],
184
197
  // `akan build` must embed production react-server-dom regardless of the shell's NODE_ENV.
185
198
  define: { "process.env.NODE_ENV": JSON.stringify("production") },
186
- plugins:
187
- akanConfig.externalLibs.length > 0 ? [this.#createExternalSpecifiersPlugin(akanConfig.externalLibs)] : [],
199
+ plugins: backendExternals.length > 0 ? [this.#createExternalSpecifiersPlugin(backendExternals)] : [],
188
200
  });
189
201
  return {
190
202
  entrypoints: backendEntryPoints.length + 1,
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+
3
+ export const SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
4
+
5
+ export interface SignalTestPreloadTarget {
6
+ cwdPath: string;
7
+ }
8
+
9
+ export async function resolveSignalTestPreloadPath(target: SignalTestPreloadTarget) {
10
+ const candidates: string[] = [];
11
+ const addResolvedPackageCandidate = (basePath: string) => {
12
+ try {
13
+ candidates.push(
14
+ path.join(path.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH),
15
+ );
16
+ } catch {
17
+ // Source workspaces and published installs can resolve Akan packages from different roots.
18
+ }
19
+ };
20
+
21
+ addResolvedPackageCandidate(target.cwdPath);
22
+ addResolvedPackageCandidate(process.cwd());
23
+ addResolvedPackageCandidate(path.dirname(Bun.main));
24
+ addResolvedPackageCandidate(import.meta.dir);
25
+
26
+ candidates.push(
27
+ path.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH),
28
+ path.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH),
29
+ path.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH),
30
+ path.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH),
31
+ path.join(path.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH),
32
+ path.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH),
33
+ );
34
+
35
+ for (const candidate of [...new Set(candidates)]) {
36
+ if (await Bun.file(candidate).exists()) return candidate;
37
+ }
38
+
39
+ throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}`);
40
+ }
@@ -3,6 +3,7 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { ApplicationBuildReporter } from "./applicationBuildReporter";
6
+ import { resolveSignalTestPreloadPath } from "./applicationTestPreload";
6
7
  import { TypeScriptDependencyScanner } from "./dependencyScanner";
7
8
  import { extractDependencies } from "./extractDeps";
8
9
  import { getModelFileData } from "./getModelFileData";
@@ -84,6 +85,26 @@ describe("extractDependencies", () => {
84
85
  });
85
86
  });
86
87
 
88
+ describe("resolveSignalTestPreloadPath", () => {
89
+ test("resolves the preload file from an installed akanjs package", async () => {
90
+ const root = await makeTempRoot();
91
+ const libDir = path.join(root, "libs/shared");
92
+ await write(
93
+ path.join(root, "node_modules/akanjs/package.json"),
94
+ JSON.stringify({
95
+ name: "akanjs",
96
+ version: "0.0.0",
97
+ exports: { "./package.json": "./package.json" },
98
+ }),
99
+ );
100
+ await write(path.join(root, "node_modules/akanjs/test/signalTest.preload.ts"), "export {};\n");
101
+
102
+ await expect(resolveSignalTestPreloadPath({ cwdPath: libDir })).resolves.toContain(
103
+ "node_modules/akanjs/test/signalTest.preload.ts",
104
+ );
105
+ });
106
+ });
107
+
87
108
  describe("TypeScriptDependencyScanner", () => {
88
109
  test("separates monorepo package, lib, runtime, and type-only dependencies", async () => {
89
110
  const root = await makeTempRoot();
package/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./akanConfig";
4
4
  export * from "./applicationBuildReporter";
5
5
  export * from "./applicationBuildRunner";
6
6
  export * from "./applicationReleasePackager";
7
+ export * from "./applicationTestPreload";
7
8
  export * from "./artifact";
8
9
  export * from "./auth";
9
10
  export * from "./builder";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.1.0-rc.9",
3
+ "version": "2.1.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -31,7 +31,7 @@
31
31
  "@langchain/deepseek": "^1.0.26",
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@trapezedev/project": "^7.1.4",
34
- "akanjs": "2.1.0-rc.8",
34
+ "akanjs": "2.1.0",
35
35
  "chalk": "^5.6.2",
36
36
  "commander": "^14.0.3",
37
37
  "daisyui": "^5.5.20",