@akanjs/devkit 3.0.0-alpha.16 → 3.0.0-alpha.18

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/README.ko.md CHANGED
@@ -56,7 +56,7 @@ await runner.build();
56
56
 
57
57
  ## 요구사항
58
58
 
59
- - [Bun](https://bun.sh) `>=1.3.13`
59
+ - [Bun](https://bun.sh) `>=1.4.0`
60
60
  - TypeScript
61
61
  - Optional peer는 Capacitor integration처럼 해당 기능을 사용할 때만 필요합니다.
62
62
 
package/README.md CHANGED
@@ -60,7 +60,7 @@ defaults, the shares they derive from `AKAN_MEMORY_LIMIT`, and what a small cont
60
60
 
61
61
  ## Requirements
62
62
 
63
- - [Bun](https://bun.sh) `>=1.3.13`
63
+ - [Bun](https://bun.sh) `>=1.4.0`
64
64
  - TypeScript
65
65
  - Optional peers are only needed for the features that use them, such as Capacitor integration.
66
66
 
package/aiEditor.ts CHANGED
@@ -19,7 +19,7 @@ import type { FileContent } from "./types";
19
19
 
20
20
  const MAX_ASK_TRY = 300;
21
21
 
22
- const deepSeekLlmModels = ["deepseek-chat", "deepseek-reasoner"] as const;
22
+ const deepSeekLlmModels = ["deepseek-v4-flash", "deepseek-v4-pro"] as const;
23
23
 
24
24
  const openAiLlmModels = ["gpt-5.5"] as const;
25
25
 
@@ -19,7 +19,6 @@ const packageJson: PackageJson = {
19
19
  react: "19.0.0",
20
20
  "react-dom": "19.0.0",
21
21
  "react-server-dom-webpack": "19.0.0",
22
- sharp: "1.0.0",
23
22
  "@external/runtime": "2.0.0",
24
23
  },
25
24
  };
@@ -144,8 +143,6 @@ describe("AkanAppConfig", () => {
144
143
  react: "19.0.0",
145
144
  "react-dom": "19.0.0",
146
145
  "react-server-dom-webpack": "19.0.0",
147
- croner: akanPackageJson.peerDependencies?.croner,
148
- sharp: "1.0.0",
149
146
  "@external/runtime": "2.0.0",
150
147
  },
151
148
  });
@@ -184,8 +181,6 @@ describe("AkanAppConfig", () => {
184
181
  react: runtimeDependencies.react,
185
182
  "react-dom": runtimeDependencies["react-dom"],
186
183
  "react-server-dom-webpack": runtimeDependencies["react-server-dom-webpack"],
187
- croner: runtimeDependencies.croner,
188
- sharp: runtimeDependencies.sharp,
189
184
  });
190
185
  });
191
186
 
@@ -198,9 +193,6 @@ describe("AkanAppConfig", () => {
198
193
  const multipleConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "multiple" }, baseDevEnv);
199
194
  const clusterConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "cluster" }, baseDevEnv);
200
195
 
201
- expect(singleConfig.getProductionPackageJson().dependencies).toMatchObject({
202
- croner: runtimeDependencies.croner,
203
- });
204
196
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("ioredis");
205
197
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("bullmq");
206
198
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("@libsql/client");
@@ -210,7 +202,6 @@ describe("AkanAppConfig", () => {
210
202
  expect(multipleConfig.getProductionPackageJson().dependencies).toMatchObject({
211
203
  "@libsql/client": runtimeDependencies["@libsql/client"],
212
204
  bullmq: runtimeDependencies.bullmq,
213
- croner: runtimeDependencies.croner,
214
205
  ioredis: runtimeDependencies.ioredis,
215
206
  protobufjs: runtimeDependencies.protobufjs,
216
207
  });
@@ -218,7 +209,6 @@ describe("AkanAppConfig", () => {
218
209
 
219
210
  expect(clusterConfig.getProductionPackageJson().dependencies).toMatchObject({
220
211
  bullmq: runtimeDependencies.bullmq,
221
- croner: runtimeDependencies.croner,
222
212
  ioredis: runtimeDependencies.ioredis,
223
213
  postgres: runtimeDependencies.postgres,
224
214
  protobufjs: runtimeDependencies.protobufjs,
@@ -50,8 +50,6 @@ const DEFAULT_OPTIMIZE_IMPORTS = [
50
50
  ];
51
51
  const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
52
52
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
53
- const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
54
- const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
55
53
  // The firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
56
54
  // `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
57
55
  // and native-platform packages that `npx cap add`/`sync` resolve from the workspace node_modules.
@@ -92,8 +90,6 @@ const DATABASE_MODE_RUNTIME_PACKAGES = {
92
90
  } satisfies Record<DatabaseMode, readonly string[]>;
93
91
  const AKAN_RUNTIME_PACKAGES = new Set<string>([
94
92
  ...SSR_RUNTIME_PACKAGES,
95
- ...NATIVE_RUNTIME_PACKAGES,
96
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
97
93
  ...MOBILE_RUNTIME_PACKAGES,
98
94
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
99
95
  ]);
@@ -428,13 +424,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
428
424
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
429
425
  }
430
426
  #getProductionRuntimePackages() {
431
- return [
432
- ...this.externalLibs,
433
- ...SSR_RUNTIME_PACKAGES,
434
- ...NATIVE_RUNTIME_PACKAGES,
435
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
436
- ...this.getDatabaseModeRuntimePackages(),
437
- ];
427
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
438
428
  }
439
429
  getDatabaseModeRuntimePackages(databaseMode: DatabaseMode = this.defaultDatabaseMode) {
440
430
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
@@ -4,7 +4,7 @@ import { AKAN_OPTIONAL_BACKEND_EXTERNALS } from "./applicationBuildRunner";
4
4
  describe("ApplicationBuildRunner", () => {
5
5
  test("externalizes Akan optional backend dependencies", () => {
6
6
  expect(AKAN_OPTIONAL_BACKEND_EXTERNALS).toEqual(
7
- expect.arrayContaining(["@libsql/client", "bullmq", "croner", "ioredis", "postgres", "protobufjs"]),
7
+ expect.arrayContaining(["@libsql/client", "bullmq", "ioredis", "postgres", "protobufjs"]),
8
8
  );
9
9
  });
10
10
  });
@@ -56,7 +56,6 @@ const SSR_RENDER_EXTERNALS = [
56
56
  export const AKAN_OPTIONAL_BACKEND_EXTERNALS = [
57
57
  "@libsql/client",
58
58
  "bullmq",
59
- "croner",
60
59
  "ioredis",
61
60
  "postgres",
62
61
  "protobufjs",
package/biome.base.json CHANGED
@@ -174,6 +174,10 @@
174
174
  "includes": ["{apps,libs}/**/*.signal.ts"],
175
175
  "plugins": ["./node_modules/@akanjs/devkit/lint/no-redeclare-predefined-endpoint.grit"]
176
176
  },
177
+ {
178
+ "includes": ["{apps,libs}/**/*.Util.tsx", "{apps,libs}/**/*.Zone.tsx"],
179
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-model-type-in-util-zone.grit"]
180
+ },
177
181
  {
178
182
  "includes": [
179
183
  "**/ui/**/*.ts",
package/executors.test.ts CHANGED
@@ -31,7 +31,6 @@ const rootPackageJson = (extra: Partial<PackageJson> = {}): PackageJson => ({
31
31
  react: "19.0.0",
32
32
  "react-dom": "19.0.0",
33
33
  "react-server-dom-webpack": "19.0.0",
34
- sharp: "1.0.0",
35
34
  lodash: "4.0.0",
36
35
  },
37
36
  devDependencies: {
@@ -796,7 +795,7 @@ describe("PkgExecutor package generation", () => {
796
795
  exports: { "./extra": { import: "./extra.ts" } },
797
796
  peerDependencies: { react: "19.0.0" },
798
797
  peerDependenciesMeta: { react: { optional: true } },
799
- optionalDependencies: { sharp: "1.0.0" },
798
+ optionalDependencies: { "@sample/native": "1.0.0" },
800
799
  });
801
800
 
802
801
  const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
@@ -806,12 +805,12 @@ describe("PkgExecutor package generation", () => {
806
805
  expect(distPackageJson).toMatchObject({
807
806
  name: "@sample/tool",
808
807
  type: "module",
809
- engines: { bun: ">=1.3.13" },
808
+ engines: { bun: ">=1.4.0" },
810
809
  dependencies: { lodash: "4.0.0" },
811
810
  devDependencies: { typescript: "6.0.0" },
812
811
  peerDependencies: { react: "19.0.0" },
813
812
  peerDependenciesMeta: { react: { optional: true } },
814
- optionalDependencies: { sharp: "1.0.0" },
813
+ optionalDependencies: { "@sample/native": "1.0.0" },
815
814
  });
816
815
  expect(distPackageJson.exports?.["."]).toEqual({
817
816
  import: "./index.ts",
package/executors.ts CHANGED
@@ -1813,7 +1813,7 @@ export class PkgExecutor extends Executor {
1813
1813
  default: "./index.ts",
1814
1814
  },
1815
1815
  },
1816
- engines: { bun: ">=1.3.13" },
1816
+ engines: { bun: ">=1.4.0" },
1817
1817
  ...dependencyMaps,
1818
1818
  };
1819
1819
  await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
@@ -82,16 +82,42 @@ describe("route client store bootstrap", () => {
82
82
  });
83
83
 
84
84
  test("bundles akan fetch into production SSR client chunks", () => {
85
- expect(RouteClientBuilder.resolveSsrClientExternalOptions("start")).toMatchObject({
85
+ expect(RouteClientBuilder.resolveSsrClientBundleOptions("start")).toMatchObject({
86
86
  external: expect.arrayContaining(["akanjs/fetch"]),
87
87
  externalSubpaths: ["akanjs/fetch"],
88
88
  });
89
89
 
90
- expect(RouteClientBuilder.resolveSsrClientExternalOptions("build")).toEqual({
90
+ expect(RouteClientBuilder.resolveSsrClientBundleOptions("build")).toEqual({
91
+ target: "bun",
91
92
  external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "react/jsx-dev-runtime"],
92
93
  });
93
94
  });
94
95
 
96
+ test("targets the server so SSR client chunks never resolve a browser export condition", async () => {
97
+ const root = await makeTempRoot();
98
+ const pkgDir = path.join(root, "node_modules/dom-conditioned-pkg");
99
+ await write(
100
+ path.join(pkgDir, "package.json"),
101
+ JSON.stringify({
102
+ name: "dom-conditioned-pkg",
103
+ type: "module",
104
+ exports: { ".": { browser: "./index.dom.js", default: "./index.js" } },
105
+ }),
106
+ );
107
+ await write(path.join(pkgDir, "index.dom.js"), 'export const element = document.createElement("i");\n');
108
+ await write(path.join(pkgDir, "index.js"), "export const element = null;\n");
109
+ const entry = path.join(root, "entry.ts");
110
+ await write(entry, 'export { element } from "dom-conditioned-pkg";\n');
111
+
112
+ for (const command of ["start", "build"] as const) {
113
+ const { target } = RouteClientBuilder.resolveSsrClientBundleOptions(command);
114
+ const built = await Bun.build({ entrypoints: [entry], target, format: "esm" });
115
+
116
+ expect(built.success).toBe(true);
117
+ expect(await built.outputs[0].text()).not.toContain("document.createElement");
118
+ }
119
+ });
120
+
95
121
  test("rewrites SSR external imports to runtime aliases", () => {
96
122
  const source = [
97
123
  'import React, { useState } from "react";',
@@ -55,6 +55,8 @@ export interface BuildClientResult {
55
55
  rscClientUrl: string;
56
56
  }
57
57
 
58
+ export type ClientBundleTarget = "browser" | "bun";
59
+
58
60
  export const CLIENT_BUNDLE_NAMING = {
59
61
  entry: "[name]-[hash].[ext]",
60
62
  chunk: "chunks/[hash].[ext]",
@@ -79,6 +81,8 @@ export interface BundleClientEntriesOptions {
79
81
  }
80
82
 
81
83
  export interface BundleClientEntriesInternalOptions extends BundleClientEntriesOptions {
84
+ /** Module-resolution target. `"bun"` for the server-executed `client-ssr` bundle, `"browser"` otherwise. */
85
+ target?: ClientBundleTarget;
82
86
  external?: readonly string[];
83
87
  externalSubpaths?: readonly string[];
84
88
  externalAliases?: Partial<Record<string, string>>;
@@ -7,6 +7,7 @@ import {
7
7
  type BundleClientEntriesInternalOptions,
8
8
  type BundleClientEntriesResult,
9
9
  CLIENT_BUNDLE_NAMING,
10
+ type ClientBundleTarget,
10
11
  type ClientManifest,
11
12
  type MetafileOutput,
12
13
  type OpaqueEntryAliases,
@@ -25,6 +26,7 @@ export class ClientEntriesBundler {
25
26
  #externalSubpaths: readonly string[];
26
27
  #externalAliases: Partial<Record<string, string>>;
27
28
  #command: "build" | "start";
29
+ #target: ClientBundleTarget;
28
30
  #outputSubdir: string;
29
31
  #reactFastRefresh: boolean;
30
32
  #artifactDir: string;
@@ -47,6 +49,7 @@ export class ClientEntriesBundler {
47
49
  this.#externalSubpaths = options.externalSubpaths ?? [];
48
50
  this.#externalAliases = options.externalAliases ?? {};
49
51
  this.#command = options.command ?? "start";
52
+ this.#target = options.target ?? "browser";
50
53
  this.#outputSubdir = options.outputSubdir ?? "client";
51
54
  this.#reactFastRefresh = options.reactFastRefresh ?? false;
52
55
  this.#artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
@@ -61,7 +64,7 @@ export class ClientEntriesBundler {
61
64
  entrypoints: this.#opaqueEntries.entries,
62
65
  outdir: this.#outdir,
63
66
  splitting: true,
64
- target: "browser",
67
+ target: this.#target,
65
68
  format: "esm",
66
69
  naming: CLIENT_BUNDLE_NAMING,
67
70
  metafile: true,
@@ -4,7 +4,7 @@ import type { BaseBuildArtifact, ClientManifest, SsrManifest } from "akanjs/serv
4
4
  import type { App } from "../commandDecorators";
5
5
  import { createBarrelImportsPlugin } from "../transforms/barrelImportsPlugin";
6
6
  import { toClientReferencePath } from "../transforms/rscUseClientTransform";
7
- import type { ClientEntryDiscovery } from "./clientBuildTypes";
7
+ import type { ClientBundleTarget, ClientEntryDiscovery } from "./clientBuildTypes";
8
8
  import { ClientEntriesBundler } from "./clientEntriesBundler";
9
9
  import { GraphClientEntryDiscovery } from "./clientEntryDiscovery";
10
10
  import { VENDOR_SPECIFIERS } from "./vendorSpecifiers";
@@ -163,12 +163,11 @@ export class RouteClientBuilder {
163
163
  }
164
164
 
165
165
  async #buildSsrBundle(bootstrapEntries: BootstrapEntries) {
166
- const externalOptions = RouteClientBuilder.resolveSsrClientExternalOptions(this.#command);
167
166
  return new ClientEntriesBundler({
168
167
  app: this.#app,
169
168
  entries: bootstrapEntries.buildEntries,
170
169
  plugins: [await createBarrelImportsPlugin(this.#app)],
171
- ...externalOptions,
170
+ ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
172
171
  outputSubdir: "client-ssr",
173
172
  command: this.#command,
174
173
  }).bundle();
@@ -239,20 +238,28 @@ export class RouteClientBuilder {
239
238
  return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
240
239
  }
241
240
 
242
- static resolveSsrClientExternalOptions(command: "build" | "start"): {
241
+ /**
242
+ * `target: "bun"` is load-bearing: these chunks are `await import()`-ed by the SSR renderer, so a dependency
243
+ * resolved through its `browser` export condition can touch `document` at module scope and throw mid-render,
244
+ * degrading the whole document to client rendering. Bun's `conditions` only adds to the target's defaults —
245
+ * `browser` still wins — so the target itself has to say server.
246
+ */
247
+ static resolveSsrClientBundleOptions(command: "build" | "start"): {
248
+ target: ClientBundleTarget;
243
249
  external: readonly string[];
244
250
  externalSubpaths?: readonly string[];
245
251
  externalAliases?: Record<string, string>;
246
252
  } {
247
253
  if (command === "start") {
248
254
  return {
255
+ target: "bun",
249
256
  external: SSR_CLIENT_EXTERNALS,
250
257
  externalSubpaths: ["akanjs/fetch"],
251
258
  externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases(),
252
259
  };
253
260
  }
254
261
 
255
- return { external: SSR_CLIENT_ALIAS_EXTERNALS };
262
+ return { target: "bun", external: SSR_CLIENT_ALIAS_EXTERNALS };
256
263
  }
257
264
 
258
265
  static resolveAkanServerEntry(): string {
@@ -110,7 +110,7 @@ export class SsrBaseArtifactBuilder {
110
110
  const ssrBundle = await new ClientEntriesBundler({
111
111
  app: this.#app,
112
112
  entries: [rscSegmentOutletEntry],
113
- ...RouteClientBuilder.resolveSsrClientExternalOptions(this.#command),
113
+ ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
114
114
  outputSubdir: "client-ssr",
115
115
  command: this.#command,
116
116
  }).bundle();
@@ -11,6 +11,7 @@ export const VENDOR_SPECIFIERS = [
11
11
  "akanjs/base",
12
12
  "akanjs/common",
13
13
  "akanjs/constant",
14
+ "akanjs/fetch",
14
15
  ] as const;
15
16
 
16
17
  export type VendorSpecifier = (typeof VENDOR_SPECIFIERS)[number];
package/getCredentials.ts CHANGED
@@ -1,5 +1,3 @@
1
- import yaml from "js-yaml";
2
-
3
1
  import type { AppExecutor } from "./executors";
4
2
  import { FileSys } from "./fileSys";
5
3
 
@@ -12,7 +10,7 @@ interface Secret {
12
10
 
13
11
  export const getCredentials = async (app: AppExecutor, environment: string): Promise<AppSecret> => {
14
12
  const content = await FileSys.readText(`${app.workspace.workspaceRoot}/infra/app/values/${app.name}-secret.yaml`);
15
- const secret = yaml.load(content) as Secret;
13
+ const secret = Bun.YAML.parse(content) as Secret;
16
14
  const appSecret = secret[environment];
17
15
  if (!appSecret) throw new Error(`No secret found for ${app.name} in ${environment}`);
18
16
  return appSecret;
@@ -0,0 +1,16 @@
1
+ engine biome(1.0)
2
+ language js(typescript, jsx)
3
+
4
+ TsReferenceType() as $ref where {
5
+ $ref <: r"cnst\.[A-Za-z_$][A-Za-z0-9_$]*",
6
+ not $ref <: within TsIndexedAccessType() as $indexed where {
7
+ $indexed <: r"cnst\.[A-Za-z_$][A-Za-z0-9_$]*\[.value.\][\s\S]*"
8
+ },
9
+ not $ref <: within TsReferenceType() as $wrapper where {
10
+ $wrapper <: r"(?:Client|Server)(?:Init|View|Edit)<[\s\S]*"
11
+ },
12
+ register_diagnostic(
13
+ span = $ref,
14
+ message = "Util and Zone are client components, so a cnst model type here is a class instance crossing the server boundary. Take an id instead (bannerId: string), or hand server data across as ClientInit / ClientView. An enum union such as cnst.AdminRole['value'] is allowed."
15
+ )
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.0.0-alpha.16",
3
+ "version": "3.0.0-alpha.18",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,7 +14,7 @@
14
14
  "directory": "pkgs/@akanjs/devkit"
15
15
  },
16
16
  "engines": {
17
- "bun": ">=1.3.13"
17
+ "bun": ">=1.4.0"
18
18
  },
19
19
  "exports": {
20
20
  ".": {
@@ -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.16",
48
+ "akanjs": "3.0.0-alpha.18",
49
49
  "chalk": "^5.6.2",
50
50
  "commander": "^14.0.3",
51
51
  "dayjs": "^1.11.20",
@@ -53,7 +53,6 @@
53
53
  "fonteditor-core": "^2.6.3",
54
54
  "ignore": "^7.0.5",
55
55
  "ink": "^6.8.0",
56
- "js-yaml": "^4.1.1",
57
56
  "ora": "^9.4.0",
58
57
  "ssh2": "^1.17.0",
59
58
  "subset-font": "^2.5.0",
@@ -65,7 +65,6 @@ const DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
65
65
  const OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set<string>([
66
66
  "@libsql/client",
67
67
  "bullmq",
68
- "croner",
69
68
  "ioredis",
70
69
  "postgres",
71
70
  "protobufjs",