@akanjs/devkit 1.0.20 → 2.1.0-rc.1

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 (195) hide show
  1. package/README.ko.md +65 -0
  2. package/README.md +62 -6
  3. package/aiEditor.ts +304 -0
  4. package/akanApp/akanApp.host.ts +393 -0
  5. package/akanApp/index.ts +1 -0
  6. package/akanConfig/akanConfig.test.ts +236 -0
  7. package/akanConfig/akanConfig.ts +384 -0
  8. package/akanConfig/index.ts +2 -0
  9. package/akanConfig/types.ts +23 -0
  10. package/applicationBuildReporter.ts +69 -0
  11. package/applicationBuildRunner.ts +302 -0
  12. package/applicationReleasePackager.ts +206 -0
  13. package/artifact/implicitRootLayout.ts +155 -0
  14. package/artifact/index.ts +1 -0
  15. package/artifact/routeSeedIndex.test.ts +98 -0
  16. package/artifact/routeSeedIndex.ts +130 -0
  17. package/auth.ts +41 -0
  18. package/builder.ts +164 -0
  19. package/capacitor.base.config.ts +88 -0
  20. package/capacitorApp.ts +440 -0
  21. package/commandDecorators/argMeta.ts +102 -0
  22. package/commandDecorators/command.ts +351 -0
  23. package/commandDecorators/commandBuilder.ts +224 -0
  24. package/commandDecorators/commandDecorators.test.ts +212 -0
  25. package/commandDecorators/commandMeta.ts +7 -0
  26. package/commandDecorators/dependencyBuilder.ts +100 -0
  27. package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
  28. package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
  29. package/commandDecorators/targetMeta.ts +31 -0
  30. package/commandDecorators/types.ts +10 -0
  31. package/constants.ts +25 -0
  32. package/createTunnel.ts +36 -0
  33. package/dependencyScanner.ts +357 -0
  34. package/devkitUtils.test.ts +259 -0
  35. package/executors.test.ts +315 -0
  36. package/executors.ts +1390 -0
  37. package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
  38. package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
  39. package/fileSys.ts +39 -0
  40. package/frontendBuild/allRoutesBuilder.ts +103 -0
  41. package/frontendBuild/buildRouteClient.test.ts +190 -0
  42. package/frontendBuild/clientBuildTypes.ts +114 -0
  43. package/frontendBuild/clientEntriesBundler.ts +303 -0
  44. package/frontendBuild/clientEntryDiscovery.ts +199 -0
  45. package/frontendBuild/csrArtifactBuilder.ts +237 -0
  46. package/frontendBuild/cssCompiler.ts +286 -0
  47. package/frontendBuild/cssImportResolver.ts +116 -0
  48. package/frontendBuild/fontOptimizer.ts +427 -0
  49. package/frontendBuild/frontendBuild.test.ts +204 -0
  50. package/frontendBuild/hmrChangeClassifier.ts +28 -0
  51. package/frontendBuild/hmrWatcher.ts +102 -0
  52. package/frontendBuild/index.ts +18 -0
  53. package/frontendBuild/pagesBundleBuilder.ts +137 -0
  54. package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
  55. package/frontendBuild/precompressArtifacts.ts +59 -0
  56. package/frontendBuild/routeClientBuilder.ts +290 -0
  57. package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
  58. package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
  59. package/frontendBuild/vendorSpecifiers.ts +16 -0
  60. package/frontendBuild/watchRootResolver.ts +28 -0
  61. package/getCredentials.ts +19 -0
  62. package/getDirname.ts +3 -0
  63. package/getModelFileData.ts +59 -0
  64. package/getRelatedCnsts.ts +313 -0
  65. package/guideline.ts +19 -0
  66. package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
  67. package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
  68. package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
  69. package/incrementalBuilder/index.ts +1 -0
  70. package/{esm/src/index.js → index.ts} +28 -15
  71. package/lint/no-deep-internal-import.grit +25 -0
  72. package/lint/no-import-client-functions.grit +32 -0
  73. package/lint/no-import-external-library.grit +21 -0
  74. package/lint/no-js-private-class-method.grit +42 -0
  75. package/lint/no-use-client-in-server.grit +7 -0
  76. package/lint/non-scalar-props-restricted.grit +13 -0
  77. package/linter.ts +271 -0
  78. package/mobile/index.ts +1 -0
  79. package/mobile/mobileTarget.test.ts +53 -0
  80. package/mobile/mobileTarget.ts +88 -0
  81. package/package.json +48 -31
  82. package/prompter.ts +72 -0
  83. package/scanInfo.ts +606 -0
  84. package/selectModel.ts +11 -0
  85. package/{esm/src/spinner.js → spinner.ts} +22 -28
  86. package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
  87. package/sshTunnel.ts +152 -0
  88. package/{esm/src/streamAi.js → streamAi.ts} +18 -12
  89. package/transforms/barrelAnalyzer.ts +278 -0
  90. package/transforms/barrelImportsPlugin.ts +504 -0
  91. package/transforms/externalizeFrameworkPlugin.ts +185 -0
  92. package/transforms/index.ts +5 -0
  93. package/transforms/rscUseClientTransform.ts +59 -0
  94. package/transforms/transforms.test.ts +208 -0
  95. package/transforms/useClientBundlePlugin.ts +47 -0
  96. package/tsconfig.json +37 -0
  97. package/typeChecker.ts +264 -0
  98. package/types.ts +44 -0
  99. package/ui/MultiScrollList.tsx +242 -0
  100. package/ui/ScrollList.tsx +107 -0
  101. package/ui/index.ts +2 -0
  102. package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
  103. package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
  104. package/cjs/index.js +0 -21
  105. package/cjs/src/aiEditor.js +0 -311
  106. package/cjs/src/auth.js +0 -72
  107. package/cjs/src/builder.js +0 -114
  108. package/cjs/src/capacitorApp.js +0 -313
  109. package/cjs/src/commandDecorators/argMeta.js +0 -88
  110. package/cjs/src/commandDecorators/command.js +0 -324
  111. package/cjs/src/commandDecorators/commandMeta.js +0 -30
  112. package/cjs/src/commandDecorators/helpFormatter.js +0 -211
  113. package/cjs/src/commandDecorators/index.js +0 -31
  114. package/cjs/src/commandDecorators/targetMeta.js +0 -57
  115. package/cjs/src/commandDecorators/types.js +0 -15
  116. package/cjs/src/constants.js +0 -46
  117. package/cjs/src/createTunnel.js +0 -49
  118. package/cjs/src/dependencyScanner.js +0 -220
  119. package/cjs/src/executors.js +0 -964
  120. package/cjs/src/extractDeps.js +0 -103
  121. package/cjs/src/fileEditor.js +0 -120
  122. package/cjs/src/getCredentials.js +0 -44
  123. package/cjs/src/getDirname.js +0 -38
  124. package/cjs/src/getModelFileData.js +0 -66
  125. package/cjs/src/getRelatedCnsts.js +0 -260
  126. package/cjs/src/guideline.js +0 -15
  127. package/cjs/src/index.js +0 -65
  128. package/cjs/src/linter.js +0 -238
  129. package/cjs/src/prompter.js +0 -85
  130. package/cjs/src/scanInfo.js +0 -491
  131. package/cjs/src/selectModel.js +0 -46
  132. package/cjs/src/spinner.js +0 -93
  133. package/cjs/src/streamAi.js +0 -62
  134. package/cjs/src/typeChecker.js +0 -207
  135. package/cjs/src/types.js +0 -15
  136. package/cjs/src/uploadRelease.js +0 -112
  137. package/cjs/src/useStdoutDimensions.js +0 -43
  138. package/esm/index.js +0 -1
  139. package/esm/src/aiEditor.js +0 -282
  140. package/esm/src/auth.js +0 -42
  141. package/esm/src/builder.js +0 -81
  142. package/esm/src/commandDecorators/argMeta.js +0 -54
  143. package/esm/src/commandDecorators/command.js +0 -290
  144. package/esm/src/commandDecorators/commandMeta.js +0 -7
  145. package/esm/src/commandDecorators/targetMeta.js +0 -33
  146. package/esm/src/commandDecorators/types.js +0 -0
  147. package/esm/src/constants.js +0 -17
  148. package/esm/src/createTunnel.js +0 -26
  149. package/esm/src/dependencyScanner.js +0 -187
  150. package/esm/src/executors.js +0 -928
  151. package/esm/src/getCredentials.js +0 -11
  152. package/esm/src/getDirname.js +0 -5
  153. package/esm/src/getModelFileData.js +0 -33
  154. package/esm/src/getRelatedCnsts.js +0 -221
  155. package/esm/src/guideline.js +0 -0
  156. package/esm/src/linter.js +0 -205
  157. package/esm/src/prompter.js +0 -51
  158. package/esm/src/scanInfo.js +0 -455
  159. package/esm/src/selectModel.js +0 -13
  160. package/esm/src/typeChecker.js +0 -174
  161. package/esm/src/types.js +0 -0
  162. package/index.d.ts +0 -1
  163. package/src/aiEditor.d.ts +0 -50
  164. package/src/auth.d.ts +0 -9
  165. package/src/builder.d.ts +0 -18
  166. package/src/capacitorApp.d.ts +0 -39
  167. package/src/commandDecorators/argMeta.d.ts +0 -67
  168. package/src/commandDecorators/command.d.ts +0 -2
  169. package/src/commandDecorators/commandMeta.d.ts +0 -2
  170. package/src/commandDecorators/helpFormatter.d.ts +0 -3
  171. package/src/commandDecorators/index.d.ts +0 -6
  172. package/src/commandDecorators/targetMeta.d.ts +0 -19
  173. package/src/commandDecorators/types.d.ts +0 -1
  174. package/src/constants.d.ts +0 -26
  175. package/src/createTunnel.d.ts +0 -8
  176. package/src/dependencyScanner.d.ts +0 -23
  177. package/src/executors.d.ts +0 -296
  178. package/src/extractDeps.d.ts +0 -7
  179. package/src/fileEditor.d.ts +0 -16
  180. package/src/getCredentials.d.ts +0 -12
  181. package/src/getDirname.d.ts +0 -1
  182. package/src/getModelFileData.d.ts +0 -16
  183. package/src/getRelatedCnsts.d.ts +0 -53
  184. package/src/guideline.d.ts +0 -19
  185. package/src/index.d.ts +0 -23
  186. package/src/linter.d.ts +0 -109
  187. package/src/prompter.d.ts +0 -14
  188. package/src/scanInfo.d.ts +0 -82
  189. package/src/selectModel.d.ts +0 -1
  190. package/src/spinner.d.ts +0 -20
  191. package/src/streamAi.d.ts +0 -6
  192. package/src/typeChecker.d.ts +0 -52
  193. package/src/types.d.ts +0 -31
  194. package/src/uploadRelease.d.ts +0 -10
  195. package/src/useStdoutDimensions.d.ts +0 -1
@@ -0,0 +1,384 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { type AkanI18nConfig, resolveAkanI18nConfig } from "akanjs/common";
5
+ import type { AkanImageConfig } from "akanjs/server";
6
+ import type { App, Lib } from "../commandDecorators";
7
+ import { WorkspaceExecutor } from "../executors";
8
+ import type { BaseDevEnv, PackageJson } from "../types";
9
+ import {
10
+ type AkanMobileConfig,
11
+ type AkanMobileTargetConfig,
12
+ type AkanRouteConfig,
13
+ type AppConfigResult,
14
+ type Arch,
15
+ archs,
16
+ type DatabaseMode,
17
+ type DeepPartial,
18
+ type DockerConfig,
19
+ type LibConfigResult,
20
+ } from "./types";
21
+
22
+ const DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui"];
23
+ const DEFAULT_OPTIMIZE_IMPORTS = [
24
+ "lucide-react",
25
+ "date-fns",
26
+ "lodash-es",
27
+ "ramda",
28
+ "antd",
29
+ "react-bootstrap",
30
+ "ahooks",
31
+ "@ant-design/icons",
32
+ "@headlessui/react",
33
+ "@headlessui-float/react",
34
+ "@heroicons/react/20/solid",
35
+ "@heroicons/react/24/solid",
36
+ "@heroicons/react/24/outline",
37
+ "@visx/visx",
38
+ "@tremor/react",
39
+ "rxjs",
40
+ "@mui/material",
41
+ "@mui/icons-material",
42
+ "recharts",
43
+ "react-use",
44
+ "@material-ui/core",
45
+ "@material-ui/icons",
46
+ "@tabler/icons-react",
47
+ "mui-core",
48
+ "react-icons/*",
49
+ ];
50
+ const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
51
+ const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
52
+ const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
53
+ const AKAN_RUNTIME_PACKAGES = new Set<string>([...SSR_RUNTIME_PACKAGES, ...NATIVE_RUNTIME_PACKAGES]);
54
+ const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
55
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
56
+ imageSizes: [32, 48, 64, 96, 128, 256, 384],
57
+ formats: ["image/webp"],
58
+ qualities: [75],
59
+ minimumCacheTTL: 14400,
60
+ remotePatterns: [],
61
+ localPatterns: [{ pathname: "/**" }],
62
+ dangerouslyAllowSVG: false,
63
+ maximumRedirects: 3,
64
+ fetchTimeoutMs: 7000,
65
+ maxRemoteBytes: 25 * 1024 * 1024,
66
+ };
67
+
68
+ export class AkanAppConfig implements AppConfigResult {
69
+ app: App;
70
+ rootPackageJson: PackageJson;
71
+ docker: DockerConfig;
72
+ defaultDatabaseMode: DatabaseMode;
73
+ externalLibs: string[];
74
+ barrelImports: string[];
75
+ optimizeImports: string[];
76
+ images: AkanImageConfig;
77
+ i18n: AkanI18nConfig;
78
+ publicEnv: string[];
79
+ mobile: AkanMobileConfig;
80
+ baseDevEnv: BaseDevEnv;
81
+ libs: string[];
82
+ domains = new Set<string>();
83
+ subRoutes = new Map<string, Set<string>>();
84
+ basePaths = new Set<string>();
85
+ branches = new Set<string>(["debug", "develop", "main"]);
86
+ constructor(
87
+ app: App,
88
+ libs: string[],
89
+ rootPackageJson: PackageJson,
90
+ config: DeepPartial<AppConfigResult>,
91
+ baseDevEnv: BaseDevEnv,
92
+ ) {
93
+ this.app = app;
94
+ this.rootPackageJson = rootPackageJson;
95
+ this.libs = libs;
96
+ this.baseDevEnv = baseDevEnv;
97
+ this.#applyRoutes(config?.routes);
98
+ this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
99
+ this.externalLibs = config?.externalLibs ?? [];
100
+ this.barrelImports = [
101
+ ...DEFAULT_BARREL_IMPORTS,
102
+ ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
103
+ ...libs.flatMap((lib) => WORKSPACE_BARREL_FACETS.map((facet) => `@libs/${lib}/${facet}`)),
104
+ ...(config?.barrelImports ?? []),
105
+ ];
106
+ this.optimizeImports = [...new Set([...DEFAULT_OPTIMIZE_IMPORTS, ...(config?.optimizeImports ?? [])])];
107
+ this.images = mergeImageConfig(config?.images as Partial<AkanImageConfig> | undefined);
108
+ this.i18n = resolveAkanI18nConfig(config?.i18n);
109
+ process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
110
+ process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
111
+ this.publicEnv = (config?.publicEnv as string[] | undefined) ?? ([] as string[]);
112
+ this.mobile = this.#resolveMobileConfig(config.mobile);
113
+ this.docker = this.#makeDockerContent(config?.docker ?? {});
114
+ }
115
+ #resolveMobileConfig(mobile: DeepPartial<AkanMobileConfig> | undefined): AkanMobileConfig {
116
+ const { targets: rawTargets, ...rawMobile } = mobile ?? {};
117
+ const appName = rawMobile.appName ?? this.app.name;
118
+ const appId = rawMobile.appId ?? `com.${this.app.name}.app`;
119
+ const version = rawMobile.version ?? "0.0.1";
120
+ const buildNum = rawMobile.buildNum ?? 1;
121
+ const defaultTargetName = this.#defaultMobileTargetName(rawTargets);
122
+ const targetEntries = Object.entries(
123
+ rawTargets ?? {
124
+ [defaultTargetName]: {},
125
+ },
126
+ );
127
+ const targets = Object.fromEntries(
128
+ targetEntries.map(([name, rawTarget]) => {
129
+ const target = rawTarget as DeepPartial<AkanMobileTargetConfig>;
130
+ const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
131
+ const basePath = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
132
+ if (basePath && !this.basePaths.has(basePath)) {
133
+ throw new Error(
134
+ `Mobile target '${name}' uses unknown basePath '${basePath}' in apps/${this.app.name}/akan.config.ts`,
135
+ );
136
+ }
137
+ const resolved = {
138
+ ...rawMobile,
139
+ ...target,
140
+ name,
141
+ basePath,
142
+ appName: target.appName ?? appName,
143
+ appId: target.appId ?? appId,
144
+ version: target.version ?? version,
145
+ buildNum: target.buildNum ?? buildNum,
146
+ plugins: {
147
+ ...rawMobile.plugins,
148
+ ...target.plugins,
149
+ },
150
+ android: {
151
+ ...rawMobile.android,
152
+ ...target.android,
153
+ },
154
+ ios: {
155
+ ...rawMobile.ios,
156
+ ...target.ios,
157
+ },
158
+ } satisfies AkanMobileTargetConfig;
159
+ return [name, resolved];
160
+ }),
161
+ );
162
+ return {
163
+ ...rawMobile,
164
+ appName,
165
+ appId,
166
+ version,
167
+ buildNum,
168
+ targets,
169
+ plugins: rawMobile.plugins,
170
+ } as AkanMobileConfig;
171
+ }
172
+ #defaultMobileTargetName(rawTargets: DeepPartial<AkanMobileConfig>["targets"] | undefined) {
173
+ if (rawTargets && Object.keys(rawTargets).length > 0) return Object.keys(rawTargets)[0] as string;
174
+ return this.basePaths.has(this.app.name) ? this.app.name : "default";
175
+ }
176
+ #applyRoutes(routes: AkanRouteConfig[] = []) {
177
+ for (const route of routes) {
178
+ if (route.basePath) {
179
+ const basePath = route.basePath.replace(/^\/+|\/+$/g, "");
180
+ this.basePaths.add(basePath);
181
+ const domains = this.subRoutes.getOrInsert(basePath, new Set());
182
+ Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
183
+ Object.values(route.domains)
184
+ .flat()
185
+ .forEach((domain) => {
186
+ if (domain) domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
187
+ });
188
+ } else {
189
+ Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
190
+ Object.values(route.domains)
191
+ .flat()
192
+ .forEach((domain) => {
193
+ if (domain) this.domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
194
+ });
195
+ }
196
+ }
197
+ const appName = this.app.name.toLowerCase();
198
+ const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
199
+ if (this.subRoutes.size === 0)
200
+ this.branches.forEach((branch) => void this.domains.add(`${appName}-${branch}.${serveDomain}`));
201
+ else
202
+ Array.from(this.subRoutes.entries()).forEach(([basePath, domains]) => {
203
+ this.branches.forEach((domain) => void domains.add(`${basePath}-${domain}.${serveDomain}`));
204
+ });
205
+ }
206
+ #getDockerRunScripts(runs: (string | { [key in Arch]?: string })[]) {
207
+ return runs.map((run) => {
208
+ if (typeof run === "string") return `RUN ${run}`;
209
+ else
210
+ return Object.entries(run)
211
+ .map(
212
+ ([arch, script]) => `RUN if [ "$TARGETARCH" = "${arch}" ]; then \
213
+ ${script}; \
214
+ fi`,
215
+ )
216
+ .join("\n");
217
+ });
218
+ }
219
+ #getDockerImageScript(image: string | { [key in Arch]?: string }, defaultImage: string) {
220
+ if (typeof image === "string") return `FROM ${image}`;
221
+ else return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join("\n");
222
+ }
223
+ #makeDockerContent(docker: DeepPartial<DockerConfig>): DockerConfig {
224
+ if (docker.content) return { content: docker.content, image: {}, preRuns: [], postRuns: [], command: [] };
225
+ const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
226
+ const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
227
+
228
+ const imageScript = docker.image
229
+ ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim")
230
+ : "FROM oven/bun:1-slim";
231
+ const command = docker.command ?? ["bun", "main.js"];
232
+ const content = `${imageScript}
233
+ RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
234
+ RUN apt-get update && apt-get upgrade -y
235
+ RUN apt-get install -y --no-install-recommends git redis build-essential python3 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils udev ffmpeg
236
+ ARG TARGETARCH
237
+ ${preRunScripts.join("\n")}
238
+ RUN mkdir -p /workspace
239
+ WORKDIR /workspace
240
+ COPY ./package.json ./package.json
241
+ RUN bun install --production
242
+ ${postRunScripts.join("\n")}
243
+ COPY . .
244
+ ENV PORT=8282
245
+ ENV NODE_ENV=production
246
+ ENV AKAN_PUBLIC_REPO_NAME=${this.baseDevEnv.repoName}
247
+ ENV AKAN_PUBLIC_SERVE_DOMAIN=${this.baseDevEnv.serveDomain}
248
+ ENV AKAN_PUBLIC_APP_NAME=${this.app.name}
249
+ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
250
+ ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
251
+ ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
252
+ ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
253
+ ENV AKAN_PUBLIC_OPERATION_MODE=cloud
254
+
255
+ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
256
+ return { content, image: imageScript, preRuns: docker.preRuns ?? [], postRuns: docker.postRuns ?? [], command };
257
+ }
258
+ static async from(app: App) {
259
+ const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
260
+ import(`${app.cwdPath}/akan.config.ts`).then((mod) => mod.default),
261
+ WorkspaceExecutor.getBaseDevEnv(path.join(app.workspace.workspaceRoot, ".env")),
262
+ app.workspace.getLibs(),
263
+ app.workspace.getPackageJson(),
264
+ ]);
265
+ const config = typeof configImp === "function" ? configImp(app) : configImp;
266
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv);
267
+ }
268
+ #resolveProductionDependencyVersion(lib: string) {
269
+ const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
270
+ if (rootVersion) return rootVersion;
271
+ const akanPackageJson = getAkanPackageJson();
272
+ if (AKAN_RUNTIME_PACKAGES.has(lib))
273
+ return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
274
+ }
275
+ getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
276
+ return {
277
+ name: this.app.name,
278
+ description: this.app.name,
279
+ version: "1.0.0",
280
+ main: "./main.js",
281
+ dependencies: Object.fromEntries(
282
+ [...new Set([...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...NATIVE_RUNTIME_PACKAGES])].map((lib) => {
283
+ const version = this.#resolveProductionDependencyVersion(lib);
284
+ if (!version) throw new Error(`Dependency ${lib} not found in package.json`);
285
+ return [lib, version];
286
+ }),
287
+ ),
288
+ ...data,
289
+ };
290
+ }
291
+ }
292
+
293
+ let akanPackageJson: PackageJson | null = null;
294
+
295
+ function getAkanPackageJson() {
296
+ if (akanPackageJson) return akanPackageJson;
297
+ const sourceDir = path.dirname(fileURLToPath(import.meta.url));
298
+ const packageJsonPaths = [
299
+ path.join(sourceDir, "../../../akanjs/package.json"),
300
+ path.join(process.cwd(), "pkgs/akanjs/package.json"),
301
+ path.join(path.dirname(Bun.main), "node_modules/akanjs/package.json"),
302
+ ];
303
+ try {
304
+ packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
305
+ } catch {
306
+ // Monorepo source execution usually resolves Akan packages through tsconfig paths, not node_modules.
307
+ }
308
+ for (const packageJsonPath of packageJsonPaths) {
309
+ try {
310
+ akanPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageJson;
311
+ return akanPackageJson;
312
+ } catch {
313
+ // Try the next known layout: source package first, bundled CLI package second.
314
+ }
315
+ }
316
+ akanPackageJson = { name: "akanjs", version: "0.0.0", description: "akanjs", dependencies: {} };
317
+ return akanPackageJson;
318
+ }
319
+
320
+ function mergeImageConfig(config: Partial<AkanImageConfig> = {}): AkanImageConfig {
321
+ return {
322
+ ...DEFAULT_AKAN_IMAGE_CONFIG,
323
+ ...config,
324
+ deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
325
+ imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
326
+ formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
327
+ qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
328
+ remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
329
+ localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns,
330
+ };
331
+ }
332
+
333
+ export class AkanLibConfig implements LibConfigResult {
334
+ lib: Lib;
335
+ externalLibs: string[];
336
+ constructor(lib: Lib, config: DeepPartial<LibConfigResult>) {
337
+ this.lib = lib;
338
+ this.externalLibs = config?.externalLibs ?? [];
339
+ }
340
+ static async from(lib: Lib) {
341
+ const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
342
+ const config = typeof configImp === "function" ? configImp(lib) : configImp;
343
+ return new AkanLibConfig(lib, config);
344
+ }
345
+ }
346
+
347
+ // export const getCapacitorConfig = (configImp: AppConfig, appInfo: AppScanResult, tsconfig: TsConfigJson) => {
348
+ // const props: RunnerProps = {
349
+ // type: "app",
350
+ // name: appInfo.name,
351
+ // repoName: appInfo.repoName,
352
+ // serveDomain: appInfo.serveDomain,
353
+ // env: (process.env.AKAN_PUBLIC_ENV ?? "debug") as "testing" | "local" | "debug" | "develop" | "main",
354
+ // libs: appInfo.libDeps,
355
+ // tsconfig,
356
+ // };
357
+ // const config = typeof configImp === "function" ? configImp(props) : configImp;
358
+ // const akanConfig = makeAppConfig(config, props);
359
+ // return akanConfig;
360
+ // };
361
+
362
+ //! need to refactor
363
+ export const increaseBuildNum = async (app: App) => {
364
+ const appConfig = await AkanAppConfig.from(app);
365
+ const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
366
+ const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
367
+ const akanConfigContent = akanConfig.replace(
368
+ `buildNum: ${appConfig.mobile.buildNum}`,
369
+ `buildNum: ${appConfig.mobile.buildNum + 1}`,
370
+ );
371
+ //? 개선할 여지가 있는지 확인
372
+ fs.writeFileSync(akanConfigPath, akanConfigContent);
373
+ };
374
+
375
+ export const decreaseBuildNum = async (app: App) => {
376
+ const appConfig = await AkanAppConfig.from(app);
377
+ const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
378
+ const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
379
+ const akanConfigContent = akanConfig.replace(
380
+ `buildNum: ${appConfig.mobile.buildNum}`,
381
+ `buildNum: ${appConfig.mobile.buildNum - 1}`,
382
+ );
383
+ fs.writeFileSync(akanConfigPath, akanConfigContent);
384
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./akanConfig";
2
+ export * from "./types";
@@ -0,0 +1,23 @@
1
+ export { archs } from "akanjs";
2
+ export type {
3
+ AkanConfigFile,
4
+ AkanMobileConfig,
5
+ AkanMobileTargetConfig,
6
+ AkanRouteConfig,
7
+ AppConfig,
8
+ AppConfigResult,
9
+ AppScanResult,
10
+ Arch,
11
+ DatabaseMode,
12
+ DeepPartial,
13
+ DockerConfig,
14
+ FileConventionScanResult,
15
+ LibConfig,
16
+ LibConfigResult,
17
+ LibScanResult,
18
+ MobileEnv,
19
+ MobilePermission,
20
+ PkgScanResult,
21
+ ScanResult,
22
+ WorkspaceScanResult,
23
+ } from "akanjs";
@@ -0,0 +1,69 @@
1
+ import { Logger } from "akanjs/common";
2
+
3
+ export interface ApplicationBuildPhaseResult {
4
+ id: string;
5
+ label: string;
6
+ durationMs: number;
7
+ summary?: string;
8
+ skipped?: boolean;
9
+ }
10
+
11
+ export interface ApplicationBuildResult {
12
+ phases: ApplicationBuildPhaseResult[];
13
+ durationMs: number;
14
+ outputDir: string;
15
+ artifactDir: string;
16
+ }
17
+
18
+ export interface ApplicationBuildProgressReporter {
19
+ phaseStart?(phase: Pick<ApplicationBuildPhaseResult, "id" | "label">): void;
20
+ phaseDone?(phase: ApplicationBuildPhaseResult): void;
21
+ phaseFail?(phase: Pick<ApplicationBuildPhaseResult, "id" | "label">, error: unknown): void;
22
+ }
23
+
24
+ export class ApplicationBuildReporter {
25
+ static create(): ApplicationBuildProgressReporter {
26
+ return {
27
+ phaseDone: (phase) => Logger.rawLog(ApplicationBuildReporter.formatPhaseLine(phase)),
28
+ };
29
+ }
30
+
31
+ static printSummary(result: ApplicationBuildResult) {
32
+ Logger.rawLog("");
33
+ Logger.rawLog(`Route artifacts: ${result.artifactDir}`);
34
+ Logger.rawLog(`Server output: ${result.outputDir}`);
35
+ Logger.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
36
+ }
37
+
38
+ static formatError(error: unknown): string {
39
+ if (error instanceof AggregateError) {
40
+ const nestedMessages = error.errors
41
+ .map(
42
+ (nestedError, index) =>
43
+ ApplicationBuildReporter.formatError(nestedError).trim() || `Unknown error ${index + 1}`,
44
+ )
45
+ .map((message) => message.replace(/^/gm, " "))
46
+ .join("\n");
47
+
48
+ return nestedMessages ? `${error.message}\n${nestedMessages}` : error.message;
49
+ }
50
+ if (error instanceof Error) {
51
+ const causeMessage = error.cause ? `\nCaused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
52
+ return `${error.message}${causeMessage}`;
53
+ }
54
+ if (typeof error === "object" && error !== null && "message" in error) return String(error.message);
55
+ return String(error);
56
+ }
57
+
58
+ static formatDuration(ms: number): string {
59
+ if (ms < 1000) return `${ms}ms`;
60
+ if (ms < 60_000) return `${Math.round(ms / 100) / 10}s`;
61
+ const seconds = Math.floor(ms / 1000);
62
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
63
+ }
64
+
65
+ static formatPhaseLine(phase: ApplicationBuildPhaseResult): string {
66
+ const summary = phase.summary ? `: ${phase.summary}` : "";
67
+ return `✓ ${phase.label}${summary} (${ApplicationBuildReporter.formatDuration(phase.durationMs)})`;
68
+ }
69
+ }