akanjs 2.0.0-rc.7 → 2.0.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.
Files changed (87) hide show
  1. package/base/primitiveRegistry.ts +28 -2
  2. package/cli/application/application.command.ts +11 -3
  3. package/cli/application/application.runner.ts +17 -1
  4. package/cli/guidelines/databaseModule/databaseModule.instruction.md +1 -1
  5. package/cli/guidelines/modelConstant/modelConstant.instruction.md +5 -5
  6. package/cli/guidelines/modelDocument/modelDocument.instruction.md +34 -61
  7. package/cli/guidelines/modelService/modelService.instruction.md +1 -1
  8. package/cli/index.js +9321 -19222
  9. package/cli/library/library.runner.ts +14 -13
  10. package/cli/package/package.runner.ts +31 -6
  11. package/cli/package/package.script.ts +2 -2
  12. package/cli/templates/app/page/_index.tsx +200 -79
  13. package/cli/templates/app/page/_layout.tsx +0 -1
  14. package/cli/templates/app/public/favicon.ico.template +0 -0
  15. package/cli/templates/app/public/logo.png.template +0 -0
  16. package/cli/templates/module/__Model__.Zone.tsx +1 -1
  17. package/cli/templates/module/__model__.document.ts +1 -1
  18. package/cli/templates/workspaceRoot/.gitignore.template +1 -11
  19. package/cli/templates/workspaceRoot/biome.json.template +16 -0
  20. package/cli/templates/workspaceRoot/package.json.template +1 -5
  21. package/cli/workspace/workspace.command.ts +7 -9
  22. package/cli/workspace/workspace.runner.ts +3 -13
  23. package/cli/workspace/workspace.script.ts +24 -9
  24. package/client/csrTypes.ts +1 -1
  25. package/constant/fieldInfo.ts +1 -1
  26. package/constant/serialize.ts +7 -1
  27. package/devkit/capacitor.base.config.ts +1 -1
  28. package/devkit/capacitorApp.ts +5 -1
  29. package/devkit/commandDecorators/argMeta.ts +28 -14
  30. package/devkit/commandDecorators/command.ts +41 -15
  31. package/devkit/commandDecorators/commandBuilder.ts +78 -42
  32. package/devkit/commandDecorators/helpFormatter.ts +7 -4
  33. package/devkit/dependencyScanner.ts +121 -15
  34. package/devkit/executors.ts +35 -23
  35. package/devkit/frontendBuild/cssCompiler.ts +9 -3
  36. package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
  37. package/devkit/lint/no-deep-internal-import.grit +25 -0
  38. package/devkit/lint/no-import-external-library.grit +1 -0
  39. package/devkit/mobile/mobileTarget.ts +48 -8
  40. package/devkit/scanInfo.ts +4 -1
  41. package/devkit/src/capacitorApp.ts +277 -0
  42. package/devkit/transforms/barrelImportsPlugin.ts +6 -0
  43. package/fetch/client/fetchClient.ts +1 -0
  44. package/fetch/client/httpClient.ts +13 -1
  45. package/package.json +37 -31
  46. package/server/akanServer.ts +21 -7
  47. package/server/hmr/clientScript.ts +8 -5
  48. package/server/resolver/resolver.contract.fixture.ts +1 -1
  49. package/test/index.ts +14 -0
  50. package/test/signalTest.preload.ts +10 -0
  51. package/test/signalTestRuntime.ts +126 -0
  52. package/test/testServer.ts +130 -25
  53. package/ui/Constant/Doc.tsx +696 -0
  54. package/ui/Constant/Mermaid.tsx +149 -0
  55. package/ui/Constant/index.ts +6 -0
  56. package/ui/Constant/schemaDoc.ts +324 -0
  57. package/ui/Field.tsx +0 -1
  58. package/ui/Portal.tsx +2 -0
  59. package/ui/System/CSR.tsx +6 -5
  60. package/ui/System/SSR.tsx +1 -1
  61. package/ui/System/SelectLanguage.tsx +1 -1
  62. package/ui/index.ts +1 -0
  63. package/ui/styles.css +0 -1
  64. package/webkit/bootCsr.tsx +8 -5
  65. package/base/test-globals.d.ts +0 -4
  66. package/cli/templates/app/common/commonLogic.ts +0 -12
  67. package/cli/templates/app/common/index.ts +0 -10
  68. package/cli/templates/app/public/favicon.ico +0 -0
  69. package/cli/templates/app/public/icons/icon-128x128.png +0 -0
  70. package/cli/templates/app/public/icons/icon-144x144.png +0 -0
  71. package/cli/templates/app/public/icons/icon-152x152.png +0 -0
  72. package/cli/templates/app/public/icons/icon-192x192.png +0 -0
  73. package/cli/templates/app/public/icons/icon-256x256.png +0 -0
  74. package/cli/templates/app/public/icons/icon-384x384.png +0 -0
  75. package/cli/templates/app/public/icons/icon-48x48.png +0 -0
  76. package/cli/templates/app/public/icons/icon-512x512.png +0 -0
  77. package/cli/templates/app/public/icons/icon-72x72.png +0 -0
  78. package/cli/templates/app/public/icons/icon-96x96.png +0 -0
  79. package/cli/templates/app/public/logo.svg +0 -70
  80. package/cli/templates/app/public/manifest.json.template +0 -67
  81. package/cli/templates/app/srvkit/backendLogic.ts +0 -12
  82. package/cli/templates/app/srvkit/index.ts +0 -10
  83. package/cli/templates/app/ui/UiComponent.ts +0 -16
  84. package/cli/templates/app/ui/index.ts +0 -10
  85. package/cli/templates/app/webkit/frontendLogic.ts +0 -12
  86. package/cli/templates/app/webkit/index.ts +0 -10
  87. package/cli/templates/module/index.tsx +0 -44
@@ -0,0 +1,277 @@
1
+ import type { CapacitorConfig } from "@capacitor/cli";
2
+ import { MobileProject } from "@trapezedev/project";
3
+ import type { AndroidProject } from "@trapezedev/project/dist/android/project";
4
+ import type { IosProject } from "@trapezedev/project/dist/ios/project";
5
+ import { capitalize } from "akanjs/common";
6
+ import { type AppExecutor, FileSys } from "akanjs/devkit";
7
+
8
+ import { FileEditor } from "./fileEditor";
9
+
10
+ interface RunConfig extends CapacitorConfig {
11
+ operation: "local" | "release";
12
+ version: string;
13
+ buildNum: number;
14
+ appId?: string;
15
+ host?: "local" | "debug" | "develop" | "main";
16
+ }
17
+
18
+ export class CapacitorApp {
19
+ project: MobileProject & { ios: IosProject; android: AndroidProject };
20
+ iosTargetName = "App";
21
+ constructor(private readonly app: AppExecutor) {
22
+ this.project = new MobileProject(this.app.cwdPath, {
23
+ android: { path: "android" },
24
+ ios: { path: "ios/App" },
25
+ }) as MobileProject & { ios: IosProject; android: AndroidProject };
26
+ }
27
+ async init() {
28
+ const project = this.project as MobileProject;
29
+ await this.project.load();
30
+ const hasAndroid = await FileSys.fileExists(`${this.app.cwdPath}/android/app/build.gradle`);
31
+ const hasIos = await FileSys.fileExists(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`);
32
+ if (!project.android && !hasAndroid) {
33
+ await this.app.spawn("npx", ["cap", "add", "android"]);
34
+ await this.project.load();
35
+ }
36
+ if (!project.ios && !hasIos) {
37
+ await this.app.spawn("npx", ["cap", "add", "ios"]);
38
+ await this.project.load();
39
+ }
40
+ return this;
41
+ }
42
+ async save() {
43
+ await this.project.commit();
44
+ }
45
+ async #prepareIos() {
46
+ const isAdded = await FileSys.fileExists(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`);
47
+ if (!isAdded) {
48
+ await this.app.spawn("npx", ["cap", "add", "ios"]);
49
+ await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
50
+ } else this.app.verbose(`iOS already added, skip adding process`);
51
+ this.app.verbose(`syncing iOS`);
52
+ await this.app.spawn("npx", ["cap", "sync", "ios"]);
53
+ this.app.verbose(`sync completed.`);
54
+ }
55
+ async buildIos() {
56
+ await this.#prepareIos();
57
+ this.app.verbose(`build completed iOS.`);
58
+ return;
59
+ }
60
+ async syncIos() {
61
+ await this.app.spawn("npx", ["cap", "sync", "ios"]);
62
+ }
63
+ async openIos() {
64
+ await this.app.spawn("npx", ["cap", "open", "ios"]);
65
+ }
66
+ async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
67
+ const defaultAppId = `com.${this.app.name}.app`;
68
+ await this.#prepareIos();
69
+ this.project.ios.setBundleId("App", "Debug", appId ?? defaultAppId);
70
+ this.project.ios.setBundleId("App", "Release", appId ?? defaultAppId);
71
+ await this.project.ios.setVersion("App", "Debug", version);
72
+ await this.project.ios.setVersion("App", "Release", version);
73
+ await this.project.ios.setBuild("App", "Debug", buildNum);
74
+ await this.project.ios.setBuild("App", "Release", buildNum);
75
+ await this.project.commit();
76
+ await this.app.spawn(
77
+ "npx",
78
+ ["cross-env", `APP_OPERATION_MODE=${operation}`, `BUN_PUBLIC_ENV=${host}`, "npx", "cap", "run", "ios"],
79
+ {
80
+ stdio: "inherit",
81
+ },
82
+ );
83
+
84
+ }
85
+
86
+ async #prepareAndroid() {
87
+ const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
88
+ if (!isAdded) {
89
+ await this.app.spawn("npx", ["cap", "add", "android"]);
90
+ } else this.app.verbose(`Android already added, skip adding process`);
91
+ await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
92
+ await this.app.spawn("npx", ["cap", "sync", "android"]);
93
+ }
94
+
95
+ async #updateAndroidBuildTypes() {
96
+
97
+ const appGradle = await FileEditor.create(`${this.app.cwdPath}/android/app/build.gradle`);
98
+ const buildTypesBlock = `
99
+ debug {
100
+ applicationIdSuffix ".debug"
101
+ versionNameSuffix "-DEBUG"
102
+ debuggable true
103
+ minifyEnabled false
104
+ }
105
+ `;
106
+ const singinConfigBlock = `
107
+ signingConfigs {
108
+ debug {
109
+ storeFile file('debug.keystore')
110
+ storePassword 'android'
111
+ keyAlias 'androiddebugkey'
112
+ keyPassword 'android'
113
+ }
114
+ release {
115
+ if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
116
+ storeFile file(MYAPP_RELEASE_STORE_FILE)
117
+ storePassword MYAPP_RELEASE_STORE_PASSWORD
118
+ keyAlias MYAPP_RELEASE_KEY_ALIAS
119
+ keyPassword MYAPP_RELEASE_KEY_PASSWORD
120
+ }
121
+ }
122
+ }
123
+ `;
124
+ if (appGradle.find("signingConfigs {") === -1) {
125
+ appGradle.insertBefore("buildTypes {", singinConfigBlock);
126
+ }
127
+ if (appGradle.find(`applicationIdSuffix ".debug"`) === -1) {
128
+ appGradle.insertAfter("buildTypes {", buildTypesBlock);
129
+ }
130
+ await appGradle.save();
131
+ }
132
+ async buildAndroid(assembleType: "apk" | "aab") {
133
+ await this.#prepareAndroid();
134
+ await this.#updateAndroidBuildTypes();
135
+
136
+ const isWindows = process.platform === "win32";
137
+ const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
138
+
139
+ await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
140
+ stdio: "inherit",
141
+ cwd: `${this.app.cwdPath}/android`,
142
+ });
143
+ }
144
+ async openAndroid() {
145
+ await this.app.spawn("npx", ["cap", "open", "android"]);
146
+ }
147
+ async syncAndroid() {
148
+ await this.#prepareAndroid();
149
+ this.app.log(`Sync Android Completed.`);
150
+ }
151
+ async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
152
+ const defaultAppId = `com.${this.app.name}.app`;
153
+ const defaultAppName = this.app.name;
154
+ await this.project.android.setVersionName(version);
155
+ await this.project.android.setPackageName(appId ?? defaultAppId);
156
+ await this.project.android.setVersionCode(buildNum);
157
+ const versionName = await this.project.android.getVersionName();
158
+ const versionCode = await this.project.android.getVersionCode();
159
+ await this.project.android.setAppName(appName ?? defaultAppName);
160
+ await this.project.commit();
161
+ await this.#prepareAndroid();
162
+
163
+ this.app.logger.info(`Running Android in ${operation} mode on ${host} host`);
164
+ await this.app.spawn(
165
+ "npx",
166
+ ["cross-env", `BUN_PUBLIC_ENV=${host}`, `APP_OPERATION_MODE=${operation}`, "npx", "cap", "run", "android"],
167
+ {
168
+ stdio: "inherit",
169
+ },
170
+ );
171
+ }
172
+
173
+ async updateAndroidVersion(version: string, buildNum: number) {
174
+
175
+ await this.project.android.setVersionName(version);
176
+ await this.project.android.setVersionCode(buildNum);
177
+ const versionName = await this.project.android.getVersionName();
178
+ const versionCode = await this.project.android.getVersionCode();
179
+ await this.project.commit();
180
+ }
181
+ async releaseIos() {
182
+
183
+ const isAdded = await Bun.file(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`).exists();
184
+ if (!isAdded) {
185
+ await this.app.spawn("npx cap add ios");
186
+ await this.app.spawn("npx @capacitor/assets generate");
187
+ } else this.app.log(`iOS already added, skip adding process`);
188
+ await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "ios"]);
189
+ }
190
+ async releaseAndroid() {
191
+
192
+ const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
193
+ if (!isAdded) {
194
+ await this.app.spawn("npx cap add android");
195
+ await this.app.spawn("npx @capacitor/assets generate");
196
+ } else this.app.log(`android already added, skip adding process`);
197
+ await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "android"]);
198
+ }
199
+ async addCamera() {
200
+ await this.#setPermissionInIos({
201
+ cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
202
+ photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
203
+ photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
204
+ });
205
+ this.#setPermissionsInAndroid(["READ_MEDIA_IMAGES", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]);
206
+ }
207
+ async addContact() {
208
+ await this.#setPermissionInIos({
209
+ contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts.",
210
+ });
211
+ this.#setPermissionsInAndroid(["READ_CONTACTS", "WRITE_CONTACTS"]);
212
+ }
213
+ async addLocation() {
214
+ await this.#setPermissionInIos({
215
+ locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
216
+ locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
217
+ });
218
+ this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
219
+ this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
220
+ }
221
+ async #setPermissionInIos(permissions: { [key: string]: string }) {
222
+ const updateNs = Object.fromEntries(
223
+ Object.entries(permissions).map(([key, value]) => [`NS${capitalize(key)}`, value]),
224
+ );
225
+ await Promise.all([
226
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
227
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs),
228
+ ]);
229
+ }
230
+ #setFeaturesInAndroid(features: string[]) {
231
+ for (const feature of features) {
232
+ if (this.#hasFeatureInAndroid(feature)) {
233
+ this.app.logger.info(`${feature} already exists in android`);
234
+ return this;
235
+ }
236
+ this.app.logger.info(`Adding ${feature} to android`);
237
+ this.project.android
238
+ .getAndroidManifest()
239
+ .injectFragment("manifest", `<uses-feature android:name="${feature}" />`);
240
+ }
241
+ return this;
242
+ }
243
+ #getFeaturesInAndroid() {
244
+ const androidManifest = this.project.android.getAndroidManifest();
245
+ const element = androidManifest.getDocumentElement();
246
+ if (!element) throw new Error("manifest not found");
247
+ const usesFeature = element.getElementsByTagName("uses-feature");
248
+ return Array.from(usesFeature).map((feature) => feature.getAttribute("android:name"));
249
+ }
250
+ #hasFeatureInAndroid(feature: string) {
251
+ return this.#getFeaturesInAndroid().includes(feature);
252
+ }
253
+
254
+ #setPermissionsInAndroid(permissions: string[]) {
255
+ for (const permission of permissions) {
256
+ if (this.#hasPermissionInAndroid(permission)) {
257
+ this.app.logger.info(`${permission} already exists in android`);
258
+ return this;
259
+ }
260
+ this.app.logger.info(`Adding ${permission} to android`);
261
+ this.project.android
262
+ .getAndroidManifest()
263
+ .injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
264
+ }
265
+ return this;
266
+ }
267
+ #getPermissionsInAndroid() {
268
+ const androidManifest = this.project.android.getAndroidManifest();
269
+ const element = androidManifest.getDocumentElement();
270
+ if (!element) throw new Error("manifest not found");
271
+ const usesPermission = element.getElementsByTagName("uses-permission");
272
+ return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
273
+ }
274
+ #hasPermissionInAndroid(permission: string) {
275
+ return this.#getPermissionsInAndroid().includes(permission);
276
+ }
277
+ }
@@ -421,6 +421,10 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
421
421
 
422
422
  const tail = ";";
423
423
 
424
+ if (shouldPreserveBarrelSideEffects(stmt.specifier)) {
425
+ lines.push(`import "${stmt.specifier}"${tail}`);
426
+ }
427
+
424
428
  if (clause.defaultImport || remaining.length > 0) {
425
429
  const parts: string[] = [];
426
430
  if (clause.defaultImport) parts.push(clause.defaultImport);
@@ -437,6 +441,8 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
437
441
  return lines.join("\n");
438
442
  };
439
443
 
444
+ const shouldPreserveBarrelSideEffects = (specifier: string): boolean => /^@(apps|libs)\/[^/]+\/client$/.test(specifier);
445
+
440
446
  const serializeNamedItem = (item: NamedImportItem): string => {
441
447
  const prefix = item.isType ? "type " : "";
442
448
  if (item.imported === item.local) return `${prefix}${item.imported}`;
@@ -495,6 +495,7 @@ export class FetchClient {
495
495
  args.forEach((arg) => {
496
496
  if (arg.type === "param") paramArgs.push(arg);
497
497
  else if (arg.type === "search") searchArgs.push(arg);
498
+ else if (arg.type === "body" && arg.refName === "Upload") uploadArgs.push(arg);
498
499
  else if (arg.type === "body") bodyArgs.push(arg);
499
500
  else if (arg.type === "upload") uploadArgs.push(arg);
500
501
  });
@@ -126,9 +126,21 @@ export class HttpClient {
126
126
  const formData = new FormData();
127
127
  uploadArgs.forEach((arg) => {
128
128
  const argValue = argMap.get(arg.name);
129
+ if (arg.nullable && (argValue === null || argValue === undefined)) return;
129
130
  if (!arg.nullable && (argValue === null || argValue === undefined))
130
131
  throw new Error(`Argument ${arg.name} is required`);
131
- formData.append(arg.name, argValue as Blob | string);
132
+ if (Array.isArray(argValue)) {
133
+ argValue.forEach((value) => {
134
+ formData.append(arg.name, value as Blob | string);
135
+ });
136
+ } else formData.append(arg.name, argValue as Blob | string);
137
+ });
138
+ bodyArgs.forEach((arg) => {
139
+ const argValue = argMap.get(arg.name);
140
+ if (arg.nullable && (argValue === null || argValue === undefined)) return;
141
+ if (!arg.nullable && (argValue === null || argValue === undefined))
142
+ throw new Error(`Argument ${arg.name} is required`);
143
+ formData.append(arg.name, typeof argValue === "string" ? argValue : JSON.stringify(argValue));
132
144
  });
133
145
  return formData;
134
146
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.0.0-rc.7",
3
+ "version": "2.0.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "bin": {
@@ -120,55 +120,62 @@
120
120
  }
121
121
  },
122
122
  "dependencies": {
123
- "@capacitor-community/contacts": "^7.1.0",
123
+ "@capacitor-community/contacts": "^7.2.0",
124
124
  "@capacitor-community/fcm": "^8.1.0",
125
125
  "@capacitor/app": "^8.1.0",
126
126
  "@capacitor/browser": "^8.0.3",
127
- "@capacitor/camera": "^8.0.2",
127
+ "@capacitor/camera": "^8.2.0",
128
+ "@capacitor/core": "^8.3.4",
128
129
  "@capacitor/device": "^8.0.2",
129
- "@capacitor/geolocation": "^8.1.0",
130
+ "@capacitor/geolocation": "^8.2.0",
130
131
  "@capacitor/haptics": "^8.0.2",
131
- "@capacitor/keyboard": "^8.0.2",
132
+ "@capacitor/keyboard": "^8.0.3",
132
133
  "@capacitor/preferences": "^8.0.1",
133
- "@capacitor/push-notifications": "^8.0.3",
134
- "@capgo/capacitor-updater": "^8.45.0",
134
+ "@capacitor/push-notifications": "^8.1.1",
135
+ "@capgo/capacitor-updater": "^8.46.1",
135
136
  "@formatjs/intl-localematcher": "^0.8.8",
136
- "@inquirer/prompts": "^8.3.2",
137
- "@langchain/core": "^1.1.42",
138
- "@langchain/deepseek": "^1.0.18",
139
- "@langchain/openai": "^1.4.5",
137
+ "@inquirer/prompts": "^8.4.3",
138
+ "@langchain/core": "^1.1.47",
139
+ "@langchain/deepseek": "^1.0.26",
140
+ "@langchain/openai": "^1.4.6",
140
141
  "@libsql/client": "^0.17.3",
141
- "@playwright/test": "^1.58.2",
142
+ "@playwright/test": "^1.60.0",
142
143
  "@radix-ui/react-dialog": "^1.1.15",
143
144
  "@react-spring/web": "^9.7.5",
144
- "@trapezedev/project": "^7.1.3",
145
+ "@trapezedev/project": "^7.1.4",
145
146
  "@use-gesture/react": "^10.3.1",
146
- "bullmq": "^5.69.3",
147
+ "bullmq": "^5.76.10",
147
148
  "capacitor-plugin-safe-area": "^5.0.0",
148
149
  "chalk": "^5.6.2",
149
150
  "chance": "^1.1.13",
150
151
  "clsx": "^2.1.1",
151
152
  "commander": "^14.0.3",
152
- "cordova-plugin-purchase": "^13.13.1",
153
+ "compare-versions": "^6.1.1",
154
+ "cordova-plugin-purchase": "^13.16.0",
153
155
  "croner": "^10.0.1",
156
+ "daisyui": "^5.5.20",
154
157
  "dataloader": "^2.2.3",
155
- "dayjs": "^1.11.19",
158
+ "dayjs": "^1.11.20",
156
159
  "file-saver": "^2.0.5",
157
160
  "fontaine": "^0.8.0",
158
161
  "fonteditor-core": "^2.6.3",
159
162
  "ignore": "^7.0.5",
160
- "immer": "^11.1.3",
163
+ "immer": "^11.1.8",
161
164
  "ink": "^6.8.0",
162
- "ioredis": "^5.9.3",
163
- "js-cookie": "^3.0.5",
165
+ "ioredis": "^5.10.1",
166
+ "js-cookie": "^3.0.7",
164
167
  "js-yaml": "^4.1.1",
165
168
  "jwt-decode": "^4.0.0",
166
- "lodash": "^4.17.23",
169
+ "latest-version": "^9.0.0",
170
+ "lodash": "^4.18.1",
171
+ "mermaid": "^11.15.0",
167
172
  "negotiator": "^1.0.0",
168
- "ora": "^9.3.0",
173
+ "open": "^11.0.0",
174
+ "ora": "^9.4.0",
169
175
  "pluralize": "^8.0.0",
170
176
  "postgres": "^3.4.9",
171
- "protobufjs": "^8.0.0",
177
+ "protobufjs": "^8.4.0",
178
+ "qrcode": "^1.5.4",
172
179
  "react": "19.2.4",
173
180
  "react-copy-to-clipboard": "^5.1.1",
174
181
  "react-datepicker": "^9.1.0",
@@ -176,22 +183,21 @@
176
183
  "react-dom": "19.2.4",
177
184
  "react-icons": "^5.6.0",
178
185
  "react-refresh": "^0.18.0",
179
- "react-server-dom-webpack": "^19.2.5",
186
+ "react-server-dom-webpack": "^19.2.6",
180
187
  "react-simple-pull-to-refresh": "^1.3.4",
181
188
  "react-spring": "^9.7.5",
189
+ "scheduler": "^0.27.0",
182
190
  "sharp": "^0.34.5",
183
191
  "ssh2": "^1.17.0",
184
192
  "subset-font": "^2.5.0",
185
- "tailwindcss": "^4.2.2",
186
- "typescript": "^6.0.3"
193
+ "tailwind-scrollbar": "^4.0.2",
194
+ "tailwindcss": "^4.3.0",
195
+ "typescript": "^6.0.3",
196
+ "uuid": "^13.0.2"
187
197
  },
188
198
  "devDependencies": {
189
- "@capacitor/cli": "^8.2.0",
190
- "@use-gesture/react": "^10.3.1",
191
- "bullmq": "^5.69.3",
192
- "dataloader": "^2.2.3",
193
- "ioredis": "^5.9.3",
194
- "react-spring": "^9.7.5"
199
+ "@biomejs/biome": "2.4.4",
200
+ "@capacitor/cli": "^8.3.4"
195
201
  },
196
202
  "bun": {
197
203
  "platform": "bun"
@@ -35,7 +35,7 @@ interface AkanAppPrepared {
35
35
  renderEnvRoutes: HttpRoutes;
36
36
  hmrHub: HmrWsHub | null;
37
37
  builderRpc: BuilderRpc | null;
38
- webRouter: WebRouter;
38
+ webRouter: WebRouter | null;
39
39
  webProxyRunner: WebProxyRunner | null;
40
40
  }
41
41
 
@@ -120,7 +120,7 @@ export class AkanServer {
120
120
  return this.#di.getAdaptor<T>(refName);
121
121
  }
122
122
 
123
- async init({ routes: initRoutes = true }: { routes?: boolean } = {}) {
123
+ async init({ routes: initRoutes = true, web = true }: { routes?: boolean; web?: boolean } = {}) {
124
124
  if (this.status !== "stopped") throw new Error("AkanServer is not able to init. It is already running.");
125
125
  this.status = "initializing";
126
126
  const { routes, wsRoutes, routeOptions } = await this.#di.initializeAll();
@@ -129,6 +129,20 @@ export class AkanServer {
129
129
  this.status = "initialized";
130
130
  return this;
131
131
  }
132
+ if (!web) {
133
+ this.#prepared = {
134
+ routes,
135
+ routeOptions,
136
+ wsRoutes,
137
+ renderEnvRoutes: {},
138
+ hmrHub: null,
139
+ builderRpc: null,
140
+ webRouter: null,
141
+ webProxyRunner: null,
142
+ };
143
+ this.status = "initialized";
144
+ return this;
145
+ }
132
146
  const { WebRouter } = await import("./webRouter");
133
147
  const webRouter = await WebRouter.create({
134
148
  upgradeHmrWs: (req, data) => this.#server?.upgrade(req, { data }) ?? false,
@@ -156,7 +170,7 @@ export class AkanServer {
156
170
  wsRoutes,
157
171
  registry: this.#di.registry,
158
172
  hmrHub,
159
- hmrState: { state: webRouter.renderState },
173
+ hmrState: webRouter ? { state: webRouter.renderState } : null,
160
174
  logger: this.logger,
161
175
  }),
162
176
 
@@ -233,10 +247,10 @@ export class AkanServer {
233
247
  return this;
234
248
  }
235
249
 
236
- async start({ listen }: { listen?: boolean } = {}) {
250
+ async start({ listen, web = true }: { listen?: boolean; web?: boolean } = {}) {
237
251
  const isScriptCommand = process.env.AKAN_COMMAND_TYPE === "script";
238
252
  const shouldListen = (listen ?? !isScriptCommand) && this.serverMode !== "batch";
239
- await this.init({ routes: shouldListen });
253
+ await this.init({ routes: shouldListen, web });
240
254
  if (!shouldListen) {
241
255
  const websocket = this.#di.getWebsocketAdaptor();
242
256
  if (websocket) SignalResolver.setLocalPublish((roomId, data) => this.#localPublish?.(roomId, data), websocket);
@@ -276,7 +290,7 @@ export class AkanServer {
276
290
  this.#server = null;
277
291
  this.#wsServer = null;
278
292
 
279
- this.#prepared?.webRouter.dispose();
293
+ this.#prepared?.webRouter?.dispose();
280
294
  await this.#withShutdownTimeout(this.#di.destroyAll());
281
295
  this.#prepared = null;
282
296
  this.status = "stopped";
@@ -313,7 +327,7 @@ export class AkanServer {
313
327
  async #reportMetrics() {
314
328
  const metrics = await ProcessMetricsCollector.collect({
315
329
  role: this.serverMode,
316
- ...(this.#prepared?.webRouter.getMetrics() ?? {}),
330
+ ...(this.#prepared?.webRouter?.getMetrics() ?? {}),
317
331
  });
318
332
  process.send?.({ type: "metrics.report", pid: process.pid, metrics } satisfies AkanIpcMessage);
319
333
  if (process.env.AKAN_MEMORY_LOG === "1") {
@@ -45,11 +45,11 @@ export const HMR_CLIENT_SCRIPT = `(function(){
45
45
  return;
46
46
  }
47
47
  if (msg.type === "rsc-refresh") {
48
- refreshRsc(msg);
48
+ reloadForHmr(msg);
49
49
  return;
50
50
  }
51
51
  if (msg.type === "client-refresh") {
52
- refreshClient(msg);
52
+ reloadForHmr(msg);
53
53
  return;
54
54
  }
55
55
  if (msg.type === "css-update") {
@@ -73,6 +73,12 @@ export const HMR_CLIENT_SCRIPT = `(function(){
73
73
  setTimeout(connect, delay);
74
74
  }
75
75
 
76
+ function reloadForHmr(msg){
77
+ try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}
78
+ if (msg && msg.buildId != null) lastBuildId = msg.buildId;
79
+ location.reload();
80
+ }
81
+
76
82
  function ensureOverlay(){
77
83
  if (overlayEl && overlayLabelEl) return overlayEl;
78
84
  if (!overlayStyleEl) {
@@ -303,8 +309,5 @@ export const HMR_CLIENT_SCRIPT = `(function(){
303
309
  return cssAssets[""] && cssAssets[""].cssUrl ? cssAssets[""].cssUrl : null;
304
310
  }
305
311
 
306
- ensureRefreshRuntime().catch(function(err){
307
- console.warn("[akan-hmr] React Refresh runtime preload failed", err);
308
- });
309
312
  connect();
310
313
  })();`;
@@ -31,7 +31,7 @@ const ServerResolverTestFull = via(ServerResolverTestObject, ServerResolverTestL
31
31
  resolvedLabel: r(String),
32
32
  }));
33
33
  const ServerResolverTestInsight = via(ServerResolverTestFull, (f) => ({
34
- total: f(Int, { default: 0, accumulate: { $sum: "$count" } }),
34
+ total: f(Int, { default: 0, accumulate: {} }),
35
35
  }));
36
36
  export const serverResolverTestConstant = ConstantRegistry.buildModel(
37
37
  "serverResolverTestItem",
package/test/index.ts CHANGED
@@ -1,2 +1,16 @@
1
1
  export { sample } from "./sample";
2
2
  export { sampleOf } from "./sampleOf";
3
+ export {
4
+ configureSignalTest,
5
+ getOrSetupSignalTestContext,
6
+ getOrSetupSignalTestFetch,
7
+ getSignalTestContext,
8
+ getSignalTestFetch,
9
+ hasSignalTestContext,
10
+ type SignalTestContext,
11
+ type SignalTestOptions,
12
+ type SignalTestTarget,
13
+ setupSignalTestTarget,
14
+ terminateSignalTestContext,
15
+ } from "./signalTestRuntime";
16
+ export { TestServer, type TestServerOptions } from "./testServer";
@@ -0,0 +1,10 @@
1
+ import { afterAll } from "bun:test";
2
+ import { terminateSignalTestContext } from "./signalTestRuntime";
3
+
4
+ const isSignalTarget = process.env.AKAN_TEST_SIGNAL === "1";
5
+
6
+ if (isSignalTarget) {
7
+ afterAll(async () => {
8
+ await terminateSignalTestContext();
9
+ });
10
+ }