@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.
- package/README.ko.md +65 -0
- package/README.md +62 -6
- package/aiEditor.ts +304 -0
- package/akanApp/akanApp.host.ts +393 -0
- package/akanApp/index.ts +1 -0
- package/akanConfig/akanConfig.test.ts +236 -0
- package/akanConfig/akanConfig.ts +384 -0
- package/akanConfig/index.ts +2 -0
- package/akanConfig/types.ts +23 -0
- package/applicationBuildReporter.ts +69 -0
- package/applicationBuildRunner.ts +302 -0
- package/applicationReleasePackager.ts +206 -0
- package/artifact/implicitRootLayout.ts +155 -0
- package/artifact/index.ts +1 -0
- package/artifact/routeSeedIndex.test.ts +98 -0
- package/artifact/routeSeedIndex.ts +130 -0
- package/auth.ts +41 -0
- package/builder.ts +164 -0
- package/capacitor.base.config.ts +88 -0
- package/capacitorApp.ts +440 -0
- package/commandDecorators/argMeta.ts +102 -0
- package/commandDecorators/command.ts +351 -0
- package/commandDecorators/commandBuilder.ts +224 -0
- package/commandDecorators/commandDecorators.test.ts +212 -0
- package/commandDecorators/commandMeta.ts +7 -0
- package/commandDecorators/dependencyBuilder.ts +100 -0
- package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
- package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
- package/commandDecorators/targetMeta.ts +31 -0
- package/commandDecorators/types.ts +10 -0
- package/constants.ts +25 -0
- package/createTunnel.ts +36 -0
- package/dependencyScanner.ts +357 -0
- package/devkitUtils.test.ts +259 -0
- package/executors.test.ts +315 -0
- package/executors.ts +1390 -0
- package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
- package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
- package/fileSys.ts +39 -0
- package/frontendBuild/allRoutesBuilder.ts +103 -0
- package/frontendBuild/buildRouteClient.test.ts +190 -0
- package/frontendBuild/clientBuildTypes.ts +114 -0
- package/frontendBuild/clientEntriesBundler.ts +303 -0
- package/frontendBuild/clientEntryDiscovery.ts +199 -0
- package/frontendBuild/csrArtifactBuilder.ts +237 -0
- package/frontendBuild/cssCompiler.ts +286 -0
- package/frontendBuild/cssImportResolver.ts +116 -0
- package/frontendBuild/fontOptimizer.ts +427 -0
- package/frontendBuild/frontendBuild.test.ts +204 -0
- package/frontendBuild/hmrChangeClassifier.ts +28 -0
- package/frontendBuild/hmrWatcher.ts +102 -0
- package/frontendBuild/index.ts +18 -0
- package/frontendBuild/pagesBundleBuilder.ts +137 -0
- package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
- package/frontendBuild/precompressArtifacts.ts +59 -0
- package/frontendBuild/routeClientBuilder.ts +290 -0
- package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
- package/frontendBuild/vendorSpecifiers.ts +16 -0
- package/frontendBuild/watchRootResolver.ts +28 -0
- package/getCredentials.ts +19 -0
- package/getDirname.ts +3 -0
- package/getModelFileData.ts +59 -0
- package/getRelatedCnsts.ts +313 -0
- package/guideline.ts +19 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
- package/incrementalBuilder/index.ts +1 -0
- package/{esm/src/index.js → index.ts} +28 -15
- package/lint/no-deep-internal-import.grit +25 -0
- package/lint/no-import-client-functions.grit +32 -0
- package/lint/no-import-external-library.grit +21 -0
- package/lint/no-js-private-class-method.grit +42 -0
- package/lint/no-use-client-in-server.grit +7 -0
- package/lint/non-scalar-props-restricted.grit +13 -0
- package/linter.ts +271 -0
- package/mobile/index.ts +1 -0
- package/mobile/mobileTarget.test.ts +53 -0
- package/mobile/mobileTarget.ts +88 -0
- package/package.json +48 -31
- package/prompter.ts +72 -0
- package/scanInfo.ts +606 -0
- package/selectModel.ts +11 -0
- package/{esm/src/spinner.js → spinner.ts} +22 -28
- package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
- package/sshTunnel.ts +152 -0
- package/{esm/src/streamAi.js → streamAi.ts} +18 -12
- package/transforms/barrelAnalyzer.ts +278 -0
- package/transforms/barrelImportsPlugin.ts +504 -0
- package/transforms/externalizeFrameworkPlugin.ts +185 -0
- package/transforms/index.ts +5 -0
- package/transforms/rscUseClientTransform.ts +59 -0
- package/transforms/transforms.test.ts +208 -0
- package/transforms/useClientBundlePlugin.ts +47 -0
- package/tsconfig.json +37 -0
- package/typeChecker.ts +264 -0
- package/types.ts +44 -0
- package/ui/MultiScrollList.tsx +242 -0
- package/ui/ScrollList.tsx +107 -0
- package/ui/index.ts +2 -0
- package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
- package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
- package/cjs/index.js +0 -21
- package/cjs/src/aiEditor.js +0 -311
- package/cjs/src/auth.js +0 -72
- package/cjs/src/builder.js +0 -114
- package/cjs/src/capacitorApp.js +0 -313
- package/cjs/src/commandDecorators/argMeta.js +0 -88
- package/cjs/src/commandDecorators/command.js +0 -324
- package/cjs/src/commandDecorators/commandMeta.js +0 -30
- package/cjs/src/commandDecorators/helpFormatter.js +0 -211
- package/cjs/src/commandDecorators/index.js +0 -31
- package/cjs/src/commandDecorators/targetMeta.js +0 -57
- package/cjs/src/commandDecorators/types.js +0 -15
- package/cjs/src/constants.js +0 -46
- package/cjs/src/createTunnel.js +0 -49
- package/cjs/src/dependencyScanner.js +0 -220
- package/cjs/src/executors.js +0 -964
- package/cjs/src/extractDeps.js +0 -103
- package/cjs/src/fileEditor.js +0 -120
- package/cjs/src/getCredentials.js +0 -44
- package/cjs/src/getDirname.js +0 -38
- package/cjs/src/getModelFileData.js +0 -66
- package/cjs/src/getRelatedCnsts.js +0 -260
- package/cjs/src/guideline.js +0 -15
- package/cjs/src/index.js +0 -65
- package/cjs/src/linter.js +0 -238
- package/cjs/src/prompter.js +0 -85
- package/cjs/src/scanInfo.js +0 -491
- package/cjs/src/selectModel.js +0 -46
- package/cjs/src/spinner.js +0 -93
- package/cjs/src/streamAi.js +0 -62
- package/cjs/src/typeChecker.js +0 -207
- package/cjs/src/types.js +0 -15
- package/cjs/src/uploadRelease.js +0 -112
- package/cjs/src/useStdoutDimensions.js +0 -43
- package/esm/index.js +0 -1
- package/esm/src/aiEditor.js +0 -282
- package/esm/src/auth.js +0 -42
- package/esm/src/builder.js +0 -81
- package/esm/src/commandDecorators/argMeta.js +0 -54
- package/esm/src/commandDecorators/command.js +0 -290
- package/esm/src/commandDecorators/commandMeta.js +0 -7
- package/esm/src/commandDecorators/targetMeta.js +0 -33
- package/esm/src/commandDecorators/types.js +0 -0
- package/esm/src/constants.js +0 -17
- package/esm/src/createTunnel.js +0 -26
- package/esm/src/dependencyScanner.js +0 -187
- package/esm/src/executors.js +0 -928
- package/esm/src/getCredentials.js +0 -11
- package/esm/src/getDirname.js +0 -5
- package/esm/src/getModelFileData.js +0 -33
- package/esm/src/getRelatedCnsts.js +0 -221
- package/esm/src/guideline.js +0 -0
- package/esm/src/linter.js +0 -205
- package/esm/src/prompter.js +0 -51
- package/esm/src/scanInfo.js +0 -455
- package/esm/src/selectModel.js +0 -13
- package/esm/src/typeChecker.js +0 -174
- package/esm/src/types.js +0 -0
- package/index.d.ts +0 -1
- package/src/aiEditor.d.ts +0 -50
- package/src/auth.d.ts +0 -9
- package/src/builder.d.ts +0 -18
- package/src/capacitorApp.d.ts +0 -39
- package/src/commandDecorators/argMeta.d.ts +0 -67
- package/src/commandDecorators/command.d.ts +0 -2
- package/src/commandDecorators/commandMeta.d.ts +0 -2
- package/src/commandDecorators/helpFormatter.d.ts +0 -3
- package/src/commandDecorators/index.d.ts +0 -6
- package/src/commandDecorators/targetMeta.d.ts +0 -19
- package/src/commandDecorators/types.d.ts +0 -1
- package/src/constants.d.ts +0 -26
- package/src/createTunnel.d.ts +0 -8
- package/src/dependencyScanner.d.ts +0 -23
- package/src/executors.d.ts +0 -296
- package/src/extractDeps.d.ts +0 -7
- package/src/fileEditor.d.ts +0 -16
- package/src/getCredentials.d.ts +0 -12
- package/src/getDirname.d.ts +0 -1
- package/src/getModelFileData.d.ts +0 -16
- package/src/getRelatedCnsts.d.ts +0 -53
- package/src/guideline.d.ts +0 -19
- package/src/index.d.ts +0 -23
- package/src/linter.d.ts +0 -109
- package/src/prompter.d.ts +0 -14
- package/src/scanInfo.d.ts +0 -82
- package/src/selectModel.d.ts +0 -1
- package/src/spinner.d.ts +0 -20
- package/src/streamAi.d.ts +0 -6
- package/src/typeChecker.d.ts +0 -52
- package/src/types.d.ts +0 -31
- package/src/uploadRelease.d.ts +0 -10
- package/src/useStdoutDimensions.d.ts +0 -1
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CapacitorConfig } from "@capacitor/cli";
|
|
2
2
|
import { MobileProject } from "@trapezedev/project";
|
|
3
|
-
import
|
|
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
|
+
|
|
4
8
|
import { FileEditor } from "./fileEditor";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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) {
|
|
8
22
|
this.project = new MobileProject(this.app.cwdPath, {
|
|
9
23
|
android: { path: "android" },
|
|
10
|
-
ios: { path: "ios/App" }
|
|
11
|
-
});
|
|
24
|
+
ios: { path: "ios/App" },
|
|
25
|
+
}) as MobileProject & { ios: IosProject; android: AndroidProject };
|
|
12
26
|
}
|
|
13
|
-
project;
|
|
14
|
-
iosTargetName = "App";
|
|
15
27
|
async init() {
|
|
16
|
-
const project = this.project;
|
|
28
|
+
const project = this.project as MobileProject;
|
|
17
29
|
await this.project.load();
|
|
18
|
-
|
|
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) {
|
|
19
33
|
await this.app.spawn("npx", ["cap", "add", "android"]);
|
|
20
34
|
await this.project.load();
|
|
21
35
|
}
|
|
22
|
-
if (!project.ios) {
|
|
36
|
+
if (!project.ios && !hasIos) {
|
|
23
37
|
await this.app.spawn("npx", ["cap", "add", "ios"]);
|
|
24
38
|
await this.project.load();
|
|
25
39
|
}
|
|
@@ -29,12 +43,11 @@ class CapacitorApp {
|
|
|
29
43
|
await this.project.commit();
|
|
30
44
|
}
|
|
31
45
|
async #prepareIos() {
|
|
32
|
-
const isAdded =
|
|
46
|
+
const isAdded = await FileSys.fileExists(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`);
|
|
33
47
|
if (!isAdded) {
|
|
34
48
|
await this.app.spawn("npx", ["cap", "add", "ios"]);
|
|
35
49
|
await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
|
|
36
|
-
} else
|
|
37
|
-
this.app.verbose(`iOS already added, skip adding process`);
|
|
50
|
+
} else this.app.verbose(`iOS already added, skip adding process`);
|
|
38
51
|
this.app.verbose(`syncing iOS`);
|
|
39
52
|
await this.app.spawn("npx", ["cap", "sync", "ios"]);
|
|
40
53
|
this.app.verbose(`sync completed.`);
|
|
@@ -50,7 +63,7 @@ class CapacitorApp {
|
|
|
50
63
|
async openIos() {
|
|
51
64
|
await this.app.spawn("npx", ["cap", "open", "ios"]);
|
|
52
65
|
}
|
|
53
|
-
async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
|
|
66
|
+
async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
|
|
54
67
|
const defaultAppId = `com.${this.app.name}.app`;
|
|
55
68
|
await this.#prepareIos();
|
|
56
69
|
this.project.ios.setBundleId("App", "Debug", appId ?? defaultAppId);
|
|
@@ -62,35 +75,29 @@ class CapacitorApp {
|
|
|
62
75
|
await this.project.commit();
|
|
63
76
|
await this.app.spawn(
|
|
64
77
|
"npx",
|
|
65
|
-
[
|
|
66
|
-
"cross-env",
|
|
67
|
-
`APP_OPERATION_MODE=${operation}`,
|
|
68
|
-
`NEXT_PUBLIC_ENV=${host}`,
|
|
69
|
-
"npx",
|
|
70
|
-
"cap",
|
|
71
|
-
"run",
|
|
72
|
-
"ios",
|
|
73
|
-
"--live-reload",
|
|
74
|
-
operation === "release" ? "" : "--live-reload",
|
|
75
|
-
operation === "release" ? "" : "--port",
|
|
76
|
-
operation === "release" ? "" : "4201"
|
|
77
|
-
],
|
|
78
|
+
["cross-env", `APP_OPERATION_MODE=${operation}`, `BUN_PUBLIC_ENV=${host}`, "npx", "cap", "run", "ios"],
|
|
78
79
|
{
|
|
79
|
-
stdio: "inherit"
|
|
80
|
-
}
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
},
|
|
81
82
|
);
|
|
83
|
+
|
|
84
|
+
// this.project.ios.incrementBuild("App", "Debug");
|
|
85
|
+
// this.project.ios.incrementBuild("App", "Release");
|
|
82
86
|
}
|
|
87
|
+
|
|
83
88
|
async #prepareAndroid() {
|
|
84
|
-
const isAdded =
|
|
89
|
+
const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
|
|
85
90
|
if (!isAdded) {
|
|
86
91
|
await this.app.spawn("npx", ["cap", "add", "android"]);
|
|
87
|
-
} else
|
|
88
|
-
this.app.verbose(`Android already added, skip adding process`);
|
|
92
|
+
} else this.app.verbose(`Android already added, skip adding process`);
|
|
89
93
|
await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
|
|
90
94
|
await this.app.spawn("npx", ["cap", "sync", "android"]);
|
|
91
95
|
}
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
|
|
97
|
+
async #updateAndroidBuildTypes() {
|
|
98
|
+
//keystore 기본 설정 및 debug, release 설정
|
|
99
|
+
|
|
100
|
+
const appGradle = await FileEditor.create(`${this.app.cwdPath}/android/app/build.gradle`);
|
|
94
101
|
const buildTypesBlock = `
|
|
95
102
|
debug {
|
|
96
103
|
applicationIdSuffix ".debug"
|
|
@@ -123,16 +130,18 @@ class CapacitorApp {
|
|
|
123
130
|
if (appGradle.find(`applicationIdSuffix ".debug"`) === -1) {
|
|
124
131
|
appGradle.insertAfter("buildTypes {", buildTypesBlock);
|
|
125
132
|
}
|
|
126
|
-
appGradle.save();
|
|
133
|
+
await appGradle.save();
|
|
127
134
|
}
|
|
128
|
-
async buildAndroid(assembleType) {
|
|
135
|
+
async buildAndroid(assembleType: "apk" | "aab") {
|
|
129
136
|
await this.#prepareAndroid();
|
|
130
|
-
this.#updateAndroidBuildTypes();
|
|
137
|
+
await this.#updateAndroidBuildTypes();
|
|
138
|
+
//윈도우는 gradlew.bat 사용
|
|
131
139
|
const isWindows = process.platform === "win32";
|
|
132
140
|
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
141
|
+
|
|
133
142
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
134
143
|
stdio: "inherit",
|
|
135
|
-
cwd: `${this.app.cwdPath}/android
|
|
144
|
+
cwd: `${this.app.cwdPath}/android`,
|
|
136
145
|
});
|
|
137
146
|
}
|
|
138
147
|
async openAndroid() {
|
|
@@ -142,7 +151,7 @@ class CapacitorApp {
|
|
|
142
151
|
await this.#prepareAndroid();
|
|
143
152
|
this.app.log(`Sync Android Completed.`);
|
|
144
153
|
}
|
|
145
|
-
async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
|
|
154
|
+
async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
|
|
146
155
|
const defaultAppId = `com.${this.app.name}.app`;
|
|
147
156
|
const defaultAppName = this.app.name;
|
|
148
157
|
await this.project.android.setVersionName(version);
|
|
@@ -153,28 +162,20 @@ class CapacitorApp {
|
|
|
153
162
|
await this.project.android.setAppName(appName ?? defaultAppName);
|
|
154
163
|
await this.project.commit();
|
|
155
164
|
await this.#prepareAndroid();
|
|
165
|
+
|
|
156
166
|
this.app.logger.info(`Running Android in ${operation} mode on ${host} host`);
|
|
157
167
|
await this.app.spawn(
|
|
158
168
|
"npx",
|
|
159
|
-
[
|
|
160
|
-
"cross-env",
|
|
161
|
-
`NEXT_PUBLIC_ENV=${host}`,
|
|
162
|
-
`APP_OPERATION_MODE=${operation}`,
|
|
163
|
-
"npx",
|
|
164
|
-
"cap",
|
|
165
|
-
"run",
|
|
166
|
-
"android",
|
|
167
|
-
operation === "release" ? "" : "--live-reload",
|
|
168
|
-
operation === "release" ? "" : "--port",
|
|
169
|
-
operation === "release" ? "" : "4201"
|
|
170
|
-
],
|
|
169
|
+
["cross-env", `BUN_PUBLIC_ENV=${host}`, `APP_OPERATION_MODE=${operation}`, "npx", "cap", "run", "android"],
|
|
171
170
|
{
|
|
172
|
-
stdio: "inherit"
|
|
173
|
-
}
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
},
|
|
174
173
|
);
|
|
175
174
|
}
|
|
175
|
+
|
|
176
176
|
//? 릴리즈시 buildNum +1 version 파라미터 받아서 업데이트
|
|
177
|
-
async updateAndroidVersion(version, buildNum) {
|
|
177
|
+
async updateAndroidVersion(version: string, buildNum: number) {
|
|
178
|
+
//TODO: 테스트 확인 필요
|
|
178
179
|
await this.project.android.setVersionName(version);
|
|
179
180
|
await this.project.android.setVersionCode(buildNum);
|
|
180
181
|
const versionName = await this.project.android.getVersionName();
|
|
@@ -182,99 +183,99 @@ class CapacitorApp {
|
|
|
182
183
|
await this.project.commit();
|
|
183
184
|
}
|
|
184
185
|
async releaseIos() {
|
|
185
|
-
|
|
186
|
+
//TODO: 작업 필요
|
|
187
|
+
const isAdded = await Bun.file(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`).exists();
|
|
186
188
|
if (!isAdded) {
|
|
187
189
|
await this.app.spawn("npx cap add ios");
|
|
188
190
|
await this.app.spawn("npx @capacitor/assets generate");
|
|
189
|
-
} else
|
|
190
|
-
this.app.log(`iOS already added, skip adding process`);
|
|
191
|
+
} else this.app.log(`iOS already added, skip adding process`);
|
|
191
192
|
await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "ios"]);
|
|
192
193
|
}
|
|
193
194
|
async releaseAndroid() {
|
|
194
|
-
|
|
195
|
+
//TODO: 작업 필요
|
|
196
|
+
const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
|
|
195
197
|
if (!isAdded) {
|
|
196
198
|
await this.app.spawn("npx cap add android");
|
|
197
199
|
await this.app.spawn("npx @capacitor/assets generate");
|
|
198
|
-
} else
|
|
199
|
-
this.app.log(`android already added, skip adding process`);
|
|
200
|
+
} else this.app.log(`android already added, skip adding process`);
|
|
200
201
|
await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "android"]);
|
|
201
202
|
}
|
|
202
203
|
async addCamera() {
|
|
203
204
|
await this.#setPermissionInIos({
|
|
204
205
|
cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
|
|
205
206
|
photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
|
|
206
|
-
photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos."
|
|
207
|
+
photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
|
|
207
208
|
});
|
|
208
209
|
this.#setPermissionsInAndroid(["READ_MEDIA_IMAGES", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]);
|
|
209
210
|
}
|
|
210
211
|
async addContact() {
|
|
211
212
|
await this.#setPermissionInIos({
|
|
212
|
-
contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts."
|
|
213
|
+
contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts.",
|
|
213
214
|
});
|
|
214
215
|
this.#setPermissionsInAndroid(["READ_CONTACTS", "WRITE_CONTACTS"]);
|
|
215
216
|
}
|
|
216
217
|
async addLocation() {
|
|
217
218
|
await this.#setPermissionInIos({
|
|
218
219
|
locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
|
|
219
|
-
locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location."
|
|
220
|
+
locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
|
|
220
221
|
});
|
|
221
222
|
this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
|
|
222
223
|
this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
|
|
223
224
|
}
|
|
224
|
-
async #setPermissionInIos(permissions) {
|
|
225
|
+
async #setPermissionInIos(permissions: { [key: string]: string }) {
|
|
225
226
|
const updateNs = Object.fromEntries(
|
|
226
|
-
Object.entries(permissions).map(([key, value]) => [`NS${capitalize(key)}`, value])
|
|
227
|
+
Object.entries(permissions).map(([key, value]) => [`NS${capitalize(key)}`, value]),
|
|
227
228
|
);
|
|
228
229
|
await Promise.all([
|
|
229
230
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
|
|
230
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
|
|
231
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs),
|
|
231
232
|
]);
|
|
232
233
|
}
|
|
233
|
-
#setFeaturesInAndroid(features) {
|
|
234
|
+
#setFeaturesInAndroid(features: string[]) {
|
|
234
235
|
for (const feature of features) {
|
|
235
236
|
if (this.#hasFeatureInAndroid(feature)) {
|
|
236
237
|
this.app.logger.info(`${feature} already exists in android`);
|
|
237
238
|
return this;
|
|
238
239
|
}
|
|
239
240
|
this.app.logger.info(`Adding ${feature} to android`);
|
|
240
|
-
this.project.android
|
|
241
|
+
this.project.android
|
|
242
|
+
.getAndroidManifest()
|
|
243
|
+
.injectFragment("manifest", `<uses-feature android:name="${feature}" />`);
|
|
241
244
|
}
|
|
242
245
|
return this;
|
|
243
246
|
}
|
|
244
247
|
#getFeaturesInAndroid() {
|
|
245
248
|
const androidManifest = this.project.android.getAndroidManifest();
|
|
246
249
|
const element = androidManifest.getDocumentElement();
|
|
247
|
-
if (!element)
|
|
248
|
-
throw new Error("manifest not found");
|
|
250
|
+
if (!element) throw new Error("manifest not found");
|
|
249
251
|
const usesFeature = element.getElementsByTagName("uses-feature");
|
|
250
252
|
return Array.from(usesFeature).map((feature) => feature.getAttribute("android:name"));
|
|
251
253
|
}
|
|
252
|
-
#hasFeatureInAndroid(feature) {
|
|
254
|
+
#hasFeatureInAndroid(feature: string) {
|
|
253
255
|
return this.#getFeaturesInAndroid().includes(feature);
|
|
254
256
|
}
|
|
255
|
-
|
|
257
|
+
|
|
258
|
+
#setPermissionsInAndroid(permissions: string[]) {
|
|
256
259
|
for (const permission of permissions) {
|
|
257
260
|
if (this.#hasPermissionInAndroid(permission)) {
|
|
258
261
|
this.app.logger.info(`${permission} already exists in android`);
|
|
259
262
|
return this;
|
|
260
263
|
}
|
|
261
264
|
this.app.logger.info(`Adding ${permission} to android`);
|
|
262
|
-
this.project.android
|
|
265
|
+
this.project.android
|
|
266
|
+
.getAndroidManifest()
|
|
267
|
+
.injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
|
|
263
268
|
}
|
|
264
269
|
return this;
|
|
265
270
|
}
|
|
266
271
|
#getPermissionsInAndroid() {
|
|
267
272
|
const androidManifest = this.project.android.getAndroidManifest();
|
|
268
273
|
const element = androidManifest.getDocumentElement();
|
|
269
|
-
if (!element)
|
|
270
|
-
throw new Error("manifest not found");
|
|
274
|
+
if (!element) throw new Error("manifest not found");
|
|
271
275
|
const usesPermission = element.getElementsByTagName("uses-permission");
|
|
272
276
|
return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
|
|
273
277
|
}
|
|
274
|
-
#hasPermissionInAndroid(permission) {
|
|
278
|
+
#hasPermissionInAndroid(permission: string) {
|
|
275
279
|
return this.#getPermissionsInAndroid().includes(permission);
|
|
276
280
|
}
|
|
277
281
|
}
|
|
278
|
-
export {
|
|
279
|
-
CapacitorApp
|
|
280
|
-
};
|
package/sshTunnel.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type Channel, Client, type ConnectConfig } from "ssh2";
|
|
2
|
+
|
|
3
|
+
export interface SshTunnelOptions {
|
|
4
|
+
localHost?: string;
|
|
5
|
+
localPort: number;
|
|
6
|
+
srcHost?: string;
|
|
7
|
+
srcPort?: number;
|
|
8
|
+
dstHost: string;
|
|
9
|
+
dstPort: number;
|
|
10
|
+
sshOptions: ConnectConfig;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SshTunnel {
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TunnelSocketData {
|
|
18
|
+
stream?: Channel;
|
|
19
|
+
pending: Buffer[];
|
|
20
|
+
closed: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type BunServer = { stop(): void };
|
|
24
|
+
type BunSocket = Bun.Socket<TunnelSocketData>;
|
|
25
|
+
|
|
26
|
+
const closeQuietly = (close: () => void) => {
|
|
27
|
+
try {
|
|
28
|
+
close();
|
|
29
|
+
} catch {
|
|
30
|
+
// The opposite side may already have closed the tunnel.
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const createSshClient = async (options: SshTunnelOptions) =>
|
|
35
|
+
new Promise<Client>((resolve, reject) => {
|
|
36
|
+
const client = new Client();
|
|
37
|
+
|
|
38
|
+
const cleanup = () => {
|
|
39
|
+
client.off("ready", onReady);
|
|
40
|
+
client.off("error", onError);
|
|
41
|
+
client.off("close", onClose);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const onReady = () => {
|
|
45
|
+
cleanup();
|
|
46
|
+
resolve(client);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const onError = (error: Error) => {
|
|
50
|
+
cleanup();
|
|
51
|
+
reject(error);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const onClose = () => {
|
|
55
|
+
cleanup();
|
|
56
|
+
reject(new Error("SSH tunnel closed before it was ready"));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
client.once("ready", onReady);
|
|
60
|
+
client.once("error", onError);
|
|
61
|
+
client.once("close", onClose);
|
|
62
|
+
client.connect({ readyTimeout: 10000, ...options.sshOptions });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const createSshTunnel = async (options: SshTunnelOptions): Promise<SshTunnel> => {
|
|
66
|
+
const client = await createSshClient(options);
|
|
67
|
+
const sockets = new Set<BunSocket>();
|
|
68
|
+
let closing = false;
|
|
69
|
+
|
|
70
|
+
const closeSocket = (socket: BunSocket, error?: Error) => {
|
|
71
|
+
sockets.delete(socket);
|
|
72
|
+
closeBunSocket(socket, error);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const server = Bun.listen<TunnelSocketData>({
|
|
77
|
+
hostname: options.localHost ?? "127.0.0.1",
|
|
78
|
+
port: options.localPort,
|
|
79
|
+
socket: {
|
|
80
|
+
open(socket) {
|
|
81
|
+
sockets.add(socket);
|
|
82
|
+
socket.data = { pending: [], closed: false };
|
|
83
|
+
|
|
84
|
+
client.forwardOut(
|
|
85
|
+
options.srcHost ?? "127.0.0.1",
|
|
86
|
+
options.srcPort ?? options.localPort,
|
|
87
|
+
options.dstHost,
|
|
88
|
+
options.dstPort,
|
|
89
|
+
(error, stream) => {
|
|
90
|
+
if (error) {
|
|
91
|
+
closeSocket(socket, error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (socket.data.closed) {
|
|
96
|
+
stream.destroy();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
socket.data.stream = stream;
|
|
101
|
+
stream.on("data", (chunk: Buffer) => socket.write(chunk));
|
|
102
|
+
stream.once("close", () => closeSocket(socket));
|
|
103
|
+
stream.once("error", (streamError: Error) => closeSocket(socket, streamError));
|
|
104
|
+
|
|
105
|
+
for (const chunk of socket.data.pending) stream.write(chunk);
|
|
106
|
+
socket.data.pending = [];
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
data(socket, chunk) {
|
|
111
|
+
const buffer = Buffer.from(chunk);
|
|
112
|
+
if (socket.data.stream) socket.data.stream.write(buffer);
|
|
113
|
+
else socket.data.pending.push(buffer);
|
|
114
|
+
},
|
|
115
|
+
close(socket) {
|
|
116
|
+
closeSocket(socket);
|
|
117
|
+
},
|
|
118
|
+
error(socket, error) {
|
|
119
|
+
closeSocket(socket, error);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const closeTunnel = (error?: Error) => {
|
|
125
|
+
if (closing) return;
|
|
126
|
+
closing = true;
|
|
127
|
+
for (const socket of sockets) closeSocket(socket, error);
|
|
128
|
+
closeQuietly(() => client.end());
|
|
129
|
+
closeBunServer(server);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
client.once("error", (error) => closeTunnel(error));
|
|
133
|
+
client.once("close", () => closeTunnel());
|
|
134
|
+
|
|
135
|
+
return { close: closeTunnel };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
closeQuietly(() => client.end());
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const closeBunSocket = (socket: BunSocket, error?: Error) => {
|
|
143
|
+
if (socket.data.closed) return;
|
|
144
|
+
socket.data.closed = true;
|
|
145
|
+
if (error) socket.data.stream?.destroy();
|
|
146
|
+
else socket.data.stream?.end();
|
|
147
|
+
closeQuietly(() => socket.end());
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const closeBunServer = (server: BunServer) => {
|
|
151
|
+
server.stop();
|
|
152
|
+
};
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { PromptTemplate } from "@langchain/core/prompts";
|
|
2
2
|
import { RunnableSequence } from "@langchain/core/runnables";
|
|
3
3
|
import { ChatOpenAI } from "@langchain/openai";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
interface StreamResponse {
|
|
6
|
+
content: string;
|
|
7
|
+
chunk?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const streamAi = async (
|
|
11
|
+
question: string,
|
|
12
|
+
callback: (chunk: string) => void = (chunk) => {
|
|
13
|
+
process.stdout.write(chunk);
|
|
14
|
+
},
|
|
15
|
+
): Promise<StreamResponse> => {
|
|
7
16
|
const createStreamingModel = (apiKey = process.env.DEEPSEEK_API_KEY) => {
|
|
8
|
-
if (!apiKey)
|
|
9
|
-
throw new Error(`process.env.DEEPSEEK_API_KEY is not set`);
|
|
17
|
+
if (!apiKey) throw new Error(`process.env.DEEPSEEK_API_KEY is not set`);
|
|
10
18
|
return new ChatOpenAI({
|
|
11
19
|
modelName: "deepseek-reasoner",
|
|
12
20
|
temperature: 0.7,
|
|
13
|
-
streaming: true,
|
|
14
|
-
|
|
15
|
-
configuration: { baseURL: "https://api.deepseek.com/v1", apiKey }
|
|
21
|
+
streaming: true, // Enable streaming
|
|
22
|
+
configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
16
23
|
});
|
|
17
24
|
};
|
|
18
25
|
const createProcessingChain = () => {
|
|
@@ -21,19 +28,18 @@ const streamAi = async (question, callback = (chunk) => {
|
|
|
21
28
|
try {
|
|
22
29
|
const chain = createProcessingChain();
|
|
23
30
|
const stream = await chain.stream({ question });
|
|
31
|
+
|
|
24
32
|
let fullResponse = "";
|
|
25
33
|
for await (const chunk of stream) {
|
|
26
34
|
const content = chunk.content;
|
|
27
35
|
if (typeof content === "string") {
|
|
28
36
|
fullResponse += content;
|
|
29
|
-
callback(content);
|
|
37
|
+
callback(content); // Send individual chunks to callback
|
|
30
38
|
}
|
|
31
39
|
}
|
|
40
|
+
|
|
32
41
|
return { content: fullResponse };
|
|
33
42
|
} catch (error) {
|
|
34
43
|
throw new Error("Failed to stream response");
|
|
35
44
|
}
|
|
36
45
|
};
|
|
37
|
-
export {
|
|
38
|
-
streamAi
|
|
39
|
-
};
|