@akanjs/cli 2.1.1-rc.0 → 2.1.1-rc.2
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/incrementalBuilder.proc.js +344 -132
- package/index.js +578 -167
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -15,10 +15,7 @@ import { ChatOpenAI } from "@langchain/openai";
|
|
|
15
15
|
import { Logger as Logger2 } from "akanjs/common";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
|
|
18
|
-
// pkgs/@akanjs/devkit/
|
|
19
|
-
import { mkdir } from "fs/promises";
|
|
20
|
-
|
|
21
|
-
// pkgs/@akanjs/devkit/constants.ts
|
|
18
|
+
// pkgs/@akanjs/devkit/cloud/constants.ts
|
|
22
19
|
var basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
|
|
23
20
|
var configPath = `${basePath}/config.json`;
|
|
24
21
|
var akanCloudHost = process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
|
|
@@ -26,9 +23,14 @@ var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE ===
|
|
|
26
23
|
var defaultHostConfig = {};
|
|
27
24
|
var defaultAkanGlobalConfig = {
|
|
28
25
|
cloudHost: {},
|
|
26
|
+
remoteEnvServers: {},
|
|
29
27
|
llm: null
|
|
30
28
|
};
|
|
31
29
|
|
|
30
|
+
// pkgs/@akanjs/devkit/cloud/globalConfig.ts
|
|
31
|
+
import { mkdir } from "fs/promises";
|
|
32
|
+
import dayjs from "dayjs";
|
|
33
|
+
|
|
32
34
|
// pkgs/@akanjs/devkit/fileSys.ts
|
|
33
35
|
import { stat } from "fs/promises";
|
|
34
36
|
import { Logger } from "akanjs/common";
|
|
@@ -67,35 +69,198 @@ class FileSys {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
// pkgs/@akanjs/devkit/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const akanConfig = await getAkanGlobalConfig();
|
|
82
|
-
return akanConfig.cloudHost[host] ?? defaultHostConfig;
|
|
83
|
-
};
|
|
84
|
-
var setHostConfig = async (host = akanCloudHost, config = {}) => {
|
|
85
|
-
const akanConfig = await getAkanGlobalConfig();
|
|
86
|
-
akanConfig.cloudHost[host] = config;
|
|
87
|
-
await setAkanGlobalConfig(akanConfig);
|
|
88
|
-
};
|
|
89
|
-
var getSelf = async (token) => {
|
|
90
|
-
try {
|
|
91
|
-
const res = await fetch(`${akanCloudUrl}/user/getSelf`, { headers: { Authorization: `Bearer ${token}` } });
|
|
92
|
-
const user = await res.json();
|
|
93
|
-
return user;
|
|
94
|
-
} catch (e) {
|
|
95
|
-
return null;
|
|
72
|
+
// pkgs/@akanjs/devkit/cloud/globalConfig.ts
|
|
73
|
+
class GlobalConfig {
|
|
74
|
+
static async#getAkanGlobalConfig() {
|
|
75
|
+
const exists = await FileSys.fileExists(configPath);
|
|
76
|
+
const akanConfig = exists ? await FileSys.readJson(configPath) : {};
|
|
77
|
+
return {
|
|
78
|
+
...defaultAkanGlobalConfig,
|
|
79
|
+
...akanConfig,
|
|
80
|
+
cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
|
|
81
|
+
remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers
|
|
82
|
+
};
|
|
96
83
|
}
|
|
97
|
-
|
|
84
|
+
static async#setAkanGlobalConfig(akanConfig) {
|
|
85
|
+
await mkdir(basePath, { recursive: true });
|
|
86
|
+
await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
|
|
87
|
+
}
|
|
88
|
+
static async getHostConfig(host = akanCloudHost) {
|
|
89
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
90
|
+
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? defaultHostConfig);
|
|
91
|
+
}
|
|
92
|
+
static async setHostConfig(host = akanCloudHost, config = {}) {
|
|
93
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
94
|
+
akanConfig.cloudHost[host] = GlobalConfig.toHostConfigDto(config);
|
|
95
|
+
await GlobalConfig.#setAkanGlobalConfig(akanConfig);
|
|
96
|
+
}
|
|
97
|
+
static async getLlmConfig() {
|
|
98
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
99
|
+
return akanConfig.llm ?? null;
|
|
100
|
+
}
|
|
101
|
+
static async setLlmConfig(llmConfig) {
|
|
102
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
103
|
+
await GlobalConfig.#setAkanGlobalConfig({ ...akanConfig, llm: llmConfig });
|
|
104
|
+
}
|
|
105
|
+
static async getRemoteEnvServers() {
|
|
106
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
107
|
+
return akanConfig.remoteEnvServers;
|
|
108
|
+
}
|
|
109
|
+
static async setRemoteEnvServer(name, config) {
|
|
110
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
111
|
+
await GlobalConfig.#setAkanGlobalConfig({
|
|
112
|
+
...akanConfig,
|
|
113
|
+
remoteEnvServers: {
|
|
114
|
+
...akanConfig.remoteEnvServers,
|
|
115
|
+
[name]: config
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
static async removeRemoteEnvServer(name) {
|
|
120
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
121
|
+
const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
|
|
122
|
+
await GlobalConfig.#setAkanGlobalConfig({
|
|
123
|
+
...akanConfig,
|
|
124
|
+
remoteEnvServers
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
static needRefreshToken(accessToken) {
|
|
128
|
+
return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
|
|
129
|
+
}
|
|
130
|
+
static toAccessToken(accessToken) {
|
|
131
|
+
return {
|
|
132
|
+
jwt: accessToken.jwt,
|
|
133
|
+
refreshToken: accessToken.refreshToken ?? null,
|
|
134
|
+
expiresAt: accessToken.expiresAt ? dayjs(accessToken.expiresAt) : null
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
static toAccessTokenDto(accessToken) {
|
|
138
|
+
return {
|
|
139
|
+
jwt: accessToken.jwt,
|
|
140
|
+
refreshToken: accessToken.refreshToken ?? null,
|
|
141
|
+
expiresAt: accessToken.expiresAt?.toString() ?? null
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
static toHostConfigDto(hostConfig) {
|
|
145
|
+
return {
|
|
146
|
+
auth: {
|
|
147
|
+
accessToken: hostConfig.auth?.accessToken ? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken) : undefined,
|
|
148
|
+
self: hostConfig.auth?.self
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
static toHostConfig(hostConfigDto) {
|
|
153
|
+
return {
|
|
154
|
+
auth: {
|
|
155
|
+
accessToken: hostConfigDto.auth?.accessToken ? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken) : undefined,
|
|
156
|
+
self: hostConfigDto.auth?.self
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// pkgs/@akanjs/devkit/cloud/cloudApi.ts
|
|
163
|
+
class HttpClient {
|
|
164
|
+
baseUrl;
|
|
165
|
+
headers = {};
|
|
166
|
+
constructor(baseUrl, headers = {}) {
|
|
167
|
+
this.baseUrl = baseUrl;
|
|
168
|
+
this.headers = headers;
|
|
169
|
+
}
|
|
170
|
+
async get(url, { headers } = {}) {
|
|
171
|
+
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
...this.headers,
|
|
175
|
+
...headers
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return response.json();
|
|
179
|
+
}
|
|
180
|
+
async getFile(url, localPath, headers) {
|
|
181
|
+
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
182
|
+
headers: { ...this.headers, ...headers }
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok)
|
|
185
|
+
throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
|
|
186
|
+
await Bun.write(localPath, response);
|
|
187
|
+
}
|
|
188
|
+
async post(url, data, { headers } = {}) {
|
|
189
|
+
const isFormData = data instanceof FormData;
|
|
190
|
+
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
body: isFormData ? data : JSON.stringify(data),
|
|
193
|
+
headers: isFormData ? { ...this.headers, ...headers } : { "Content-Type": "application/json", ...this.headers, ...headers }
|
|
194
|
+
});
|
|
195
|
+
return response.json();
|
|
196
|
+
}
|
|
197
|
+
setHeaders(headers) {
|
|
198
|
+
Object.assign(this.headers, headers);
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
98
202
|
|
|
203
|
+
class CloudApi {
|
|
204
|
+
#api;
|
|
205
|
+
#accessToken = null;
|
|
206
|
+
static async fromHost(host) {
|
|
207
|
+
const hostConfig = await GlobalConfig.getHostConfig(host);
|
|
208
|
+
return new CloudApi(hostConfig);
|
|
209
|
+
}
|
|
210
|
+
constructor(hostConfig) {
|
|
211
|
+
const host = akanCloudHost;
|
|
212
|
+
this.#api = new HttpClient(`${host}/api`);
|
|
213
|
+
this.#accessToken = hostConfig.auth?.accessToken ?? null;
|
|
214
|
+
if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
|
|
215
|
+
this.#api.setHeaders({
|
|
216
|
+
Authorization: `Bearer ${this.#accessToken.jwt}`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async uploadEnv(devProjectId, file) {
|
|
220
|
+
const formData = new FormData;
|
|
221
|
+
formData.append("devProjectId", devProjectId);
|
|
222
|
+
formData.append("file", file);
|
|
223
|
+
const data = await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
|
|
224
|
+
return data;
|
|
225
|
+
}
|
|
226
|
+
async downloadEnv(devProjectId, localPath) {
|
|
227
|
+
await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
|
|
228
|
+
}
|
|
229
|
+
async getRemoteAuthToken(remoteId) {
|
|
230
|
+
try {
|
|
231
|
+
if (this.#accessToken) {
|
|
232
|
+
if (GlobalConfig.needRefreshToken(this.#accessToken))
|
|
233
|
+
return await this.refreshAuthToken();
|
|
234
|
+
else
|
|
235
|
+
return await this.refreshAuthToken();
|
|
236
|
+
}
|
|
237
|
+
const accessToken = await this.#api.get(`/getRemoteAuthToken/${remoteId}`);
|
|
238
|
+
this.#accessToken = GlobalConfig.toAccessToken(accessToken);
|
|
239
|
+
this.#api.setHeaders({
|
|
240
|
+
Authorization: `Bearer ${this.#accessToken.jwt}`
|
|
241
|
+
});
|
|
242
|
+
return this.#accessToken;
|
|
243
|
+
} catch (_) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async refreshAuthToken() {
|
|
248
|
+
const response = await this.#api.post(`/refreshRemoteAuthToken`, {
|
|
249
|
+
refreshToken: this.#accessToken?.refreshToken
|
|
250
|
+
});
|
|
251
|
+
this.#accessToken = GlobalConfig.toAccessToken(response);
|
|
252
|
+
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
253
|
+
return this.#accessToken;
|
|
254
|
+
}
|
|
255
|
+
async getRemoteSelf() {
|
|
256
|
+
try {
|
|
257
|
+
const data = await this.#api.get(`/getRemoteSelf`);
|
|
258
|
+
return data;
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
99
264
|
// pkgs/@akanjs/devkit/spinner.ts
|
|
100
265
|
import ora from "ora";
|
|
101
266
|
|
|
@@ -158,12 +323,18 @@ class Spinner {
|
|
|
158
323
|
|
|
159
324
|
// pkgs/@akanjs/devkit/aiEditor.ts
|
|
160
325
|
var MAX_ASK_TRY = 300;
|
|
161
|
-
var supportedLlmModels = [
|
|
326
|
+
var supportedLlmModels = [
|
|
327
|
+
"deepseek-chat",
|
|
328
|
+
"deepseek-reasoner"
|
|
329
|
+
];
|
|
162
330
|
|
|
163
331
|
class AiSession {
|
|
164
332
|
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
165
333
|
static #chat = null;
|
|
166
|
-
static async init({
|
|
334
|
+
static async init({
|
|
335
|
+
temperature = 0,
|
|
336
|
+
useExisting = true
|
|
337
|
+
} = {}) {
|
|
167
338
|
if (useExisting) {
|
|
168
339
|
const llmConfig2 = await AiSession.getLlmConfig();
|
|
169
340
|
if (llmConfig2) {
|
|
@@ -190,22 +361,24 @@ class AiSession {
|
|
|
190
361
|
return AiSession;
|
|
191
362
|
}
|
|
192
363
|
static async getLlmConfig() {
|
|
193
|
-
|
|
194
|
-
return akanConfig.llm ?? null;
|
|
364
|
+
return await GlobalConfig.getLlmConfig();
|
|
195
365
|
}
|
|
196
366
|
static async setLlmConfig(llmConfig) {
|
|
197
|
-
|
|
198
|
-
akanConfig.llm = llmConfig;
|
|
199
|
-
await setAkanGlobalConfig(akanConfig);
|
|
367
|
+
await GlobalConfig.setLlmConfig(llmConfig);
|
|
200
368
|
return AiSession;
|
|
201
369
|
}
|
|
202
370
|
static async#requestLlmConfig() {
|
|
203
|
-
const model = await select({
|
|
371
|
+
const model = await select({
|
|
372
|
+
message: "Select a LLM model",
|
|
373
|
+
choices: supportedLlmModels
|
|
374
|
+
});
|
|
204
375
|
const apiKey = await input({ message: "Enter your API key" });
|
|
205
376
|
return { model, apiKey };
|
|
206
377
|
}
|
|
207
378
|
static async#validateApiKey(modelName, apiKey) {
|
|
208
|
-
const spinner = new Spinner("Validating LLM API key...", {
|
|
379
|
+
const spinner = new Spinner("Validating LLM API key...", {
|
|
380
|
+
prefix: `\uD83E\uDD16akan-editor`
|
|
381
|
+
}).start();
|
|
209
382
|
const chat = new ChatOpenAI({
|
|
210
383
|
modelName,
|
|
211
384
|
temperature: 0,
|
|
@@ -228,7 +401,11 @@ class AiSession {
|
|
|
228
401
|
sessionKey;
|
|
229
402
|
isCacheLoaded = false;
|
|
230
403
|
workspace;
|
|
231
|
-
constructor(type, {
|
|
404
|
+
constructor(type, {
|
|
405
|
+
workspace,
|
|
406
|
+
cacheKey,
|
|
407
|
+
isContinued
|
|
408
|
+
}) {
|
|
232
409
|
this.workspace = workspace;
|
|
233
410
|
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
234
411
|
if (isContinued)
|
|
@@ -303,7 +480,13 @@ class AiSession {
|
|
|
303
480
|
throw new Error("Failed to stream response");
|
|
304
481
|
}
|
|
305
482
|
}
|
|
306
|
-
async edit(question, {
|
|
483
|
+
async edit(question, {
|
|
484
|
+
onChunk,
|
|
485
|
+
onReasoning,
|
|
486
|
+
maxTry = MAX_ASK_TRY,
|
|
487
|
+
validate,
|
|
488
|
+
approve
|
|
489
|
+
} = {}) {
|
|
307
490
|
for (let tryCount = 0;tryCount < maxTry; tryCount++) {
|
|
308
491
|
let response = await this.ask(question, { onChunk, onReasoning });
|
|
309
492
|
if (validate?.length && tryCount === 0) {
|
|
@@ -356,7 +539,9 @@ ${validate.map((v) => `- ${v}`).join(`
|
|
|
356
539
|
async#tryFixTypescripts(writes, executor, options = {}) {
|
|
357
540
|
const MAX_EDIT_TRY = 5;
|
|
358
541
|
for (let tryCount = 0;tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
359
|
-
const loader = new Spinner(`Type checking and linting...`, {
|
|
542
|
+
const loader = new Spinner(`Type checking and linting...`, {
|
|
543
|
+
prefix: `\uD83E\uDD16akan-editor`
|
|
544
|
+
}).start();
|
|
360
545
|
const fileChecks = await Promise.all(writes.map(async ({ filePath }) => {
|
|
361
546
|
const typeCheckResult = executor.typeCheck(filePath);
|
|
362
547
|
const lintResult = await executor.lint(filePath);
|
|
@@ -2164,11 +2349,26 @@ class Executor {
|
|
|
2164
2349
|
});
|
|
2165
2350
|
return new Promise((resolve, reject) => {
|
|
2166
2351
|
proc.on("error", (error) => {
|
|
2167
|
-
reject(new CommandExecutionError({
|
|
2352
|
+
reject(new CommandExecutionError({
|
|
2353
|
+
command,
|
|
2354
|
+
cwd,
|
|
2355
|
+
code: null,
|
|
2356
|
+
signal: null,
|
|
2357
|
+
stdout,
|
|
2358
|
+
stderr,
|
|
2359
|
+
cause: error
|
|
2360
|
+
}));
|
|
2168
2361
|
});
|
|
2169
2362
|
proc.on("exit", (code, signal) => {
|
|
2170
2363
|
if (!!code || signal)
|
|
2171
|
-
reject(new CommandExecutionError({
|
|
2364
|
+
reject(new CommandExecutionError({
|
|
2365
|
+
command,
|
|
2366
|
+
cwd,
|
|
2367
|
+
code,
|
|
2368
|
+
signal,
|
|
2369
|
+
stdout,
|
|
2370
|
+
stderr
|
|
2371
|
+
}));
|
|
2172
2372
|
else
|
|
2173
2373
|
resolve({ code, signal });
|
|
2174
2374
|
});
|
|
@@ -2194,11 +2394,28 @@ class Executor {
|
|
|
2194
2394
|
});
|
|
2195
2395
|
return new Promise((resolve, reject) => {
|
|
2196
2396
|
proc.on("error", (error) => {
|
|
2197
|
-
reject(new CommandExecutionError({
|
|
2397
|
+
reject(new CommandExecutionError({
|
|
2398
|
+
command,
|
|
2399
|
+
args,
|
|
2400
|
+
cwd,
|
|
2401
|
+
code: null,
|
|
2402
|
+
signal: null,
|
|
2403
|
+
stdout,
|
|
2404
|
+
stderr,
|
|
2405
|
+
cause: error
|
|
2406
|
+
}));
|
|
2198
2407
|
});
|
|
2199
2408
|
proc.on("close", (code, signal) => {
|
|
2200
2409
|
if (code !== 0 || signal)
|
|
2201
|
-
reject(new CommandExecutionError({
|
|
2410
|
+
reject(new CommandExecutionError({
|
|
2411
|
+
command,
|
|
2412
|
+
args,
|
|
2413
|
+
cwd,
|
|
2414
|
+
code,
|
|
2415
|
+
signal,
|
|
2416
|
+
stdout,
|
|
2417
|
+
stderr
|
|
2418
|
+
}));
|
|
2202
2419
|
else
|
|
2203
2420
|
resolve(stdout);
|
|
2204
2421
|
});
|
|
@@ -2242,7 +2459,15 @@ class Executor {
|
|
|
2242
2459
|
});
|
|
2243
2460
|
proc.on("exit", (code, signal) => {
|
|
2244
2461
|
if (!!code || signal)
|
|
2245
|
-
reject(new CommandExecutionError({
|
|
2462
|
+
reject(new CommandExecutionError({
|
|
2463
|
+
command: modulePath,
|
|
2464
|
+
args,
|
|
2465
|
+
cwd,
|
|
2466
|
+
code,
|
|
2467
|
+
signal,
|
|
2468
|
+
stdout,
|
|
2469
|
+
stderr
|
|
2470
|
+
}));
|
|
2246
2471
|
else
|
|
2247
2472
|
resolve({ code, signal });
|
|
2248
2473
|
});
|
|
@@ -2389,7 +2614,10 @@ class Executor {
|
|
|
2389
2614
|
const result = {
|
|
2390
2615
|
...extendsTsconfig,
|
|
2391
2616
|
...tsconfig,
|
|
2392
|
-
compilerOptions: {
|
|
2617
|
+
compilerOptions: {
|
|
2618
|
+
...extendsTsconfig.compilerOptions,
|
|
2619
|
+
...tsconfig.compilerOptions
|
|
2620
|
+
}
|
|
2393
2621
|
};
|
|
2394
2622
|
this.#tsconfig = result;
|
|
2395
2623
|
return result;
|
|
@@ -2444,7 +2672,9 @@ class Executor {
|
|
|
2444
2672
|
const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
|
|
2445
2673
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
2446
2674
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2447
|
-
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
2675
|
+
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
2676
|
+
overwrite
|
|
2677
|
+
});
|
|
2448
2678
|
} else if (staticTemplateFileExtensions.has(path7.extname(targetPath).toLowerCase())) {
|
|
2449
2679
|
const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
2450
2680
|
const writePath = this.getPath(convertedTargetPath);
|
|
@@ -2470,14 +2700,24 @@ class Executor {
|
|
|
2470
2700
|
const prefixTemplatePath = templatePath;
|
|
2471
2701
|
if ((await stat2(prefixTemplatePath)).isFile()) {
|
|
2472
2702
|
const filename = path7.basename(prefixTemplatePath);
|
|
2473
|
-
const fileContent = await this.#applyTemplateFile({
|
|
2703
|
+
const fileContent = await this.#applyTemplateFile({
|
|
2704
|
+
templatePath: prefixTemplatePath,
|
|
2705
|
+
targetPath: path7.join(basePath2, filename),
|
|
2706
|
+
scanInfo,
|
|
2707
|
+
overwrite
|
|
2708
|
+
}, dict, options);
|
|
2474
2709
|
return fileContent ? [fileContent] : [];
|
|
2475
2710
|
} else {
|
|
2476
2711
|
const subdirs = await readDirEntries(templatePath);
|
|
2477
2712
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
2478
2713
|
const subpath = path7.join(templatePath, subdir);
|
|
2479
2714
|
if ((await stat2(subpath)).isFile()) {
|
|
2480
|
-
const fileContent = await this.#applyTemplateFile({
|
|
2715
|
+
const fileContent = await this.#applyTemplateFile({
|
|
2716
|
+
templatePath: subpath,
|
|
2717
|
+
targetPath: path7.join(basePath2, subdir),
|
|
2718
|
+
scanInfo,
|
|
2719
|
+
overwrite
|
|
2720
|
+
}, dict, options);
|
|
2481
2721
|
return fileContent ? [fileContent] : [];
|
|
2482
2722
|
} else
|
|
2483
2723
|
return await this._applyTemplate({
|
|
@@ -2528,7 +2768,10 @@ class Executor {
|
|
|
2528
2768
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
2529
2769
|
const path8 = this.getPath(filePath);
|
|
2530
2770
|
const linter = this.getLinter();
|
|
2531
|
-
const { results, errors, warnings } = await linter.lint(path8, {
|
|
2771
|
+
const { results, errors, warnings } = await linter.lint(path8, {
|
|
2772
|
+
fix,
|
|
2773
|
+
dryRun
|
|
2774
|
+
});
|
|
2532
2775
|
const message = linter.formatLintResults(results);
|
|
2533
2776
|
return { results, message, errors, warnings };
|
|
2534
2777
|
}
|
|
@@ -2554,6 +2797,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2554
2797
|
const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
|
|
2555
2798
|
const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
|
|
2556
2799
|
const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
|
|
2800
|
+
const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
|
|
2557
2801
|
const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME;
|
|
2558
2802
|
if (!repoName)
|
|
2559
2803
|
throw new Error("AKAN_PUBLIC_REPO_NAME is not set");
|
|
@@ -2564,7 +2808,15 @@ class WorkspaceExecutor extends Executor {
|
|
|
2564
2808
|
const env = sourceEnv.AKAN_PUBLIC_ENV ?? "debug";
|
|
2565
2809
|
if (!env)
|
|
2566
2810
|
throw new Error("AKAN_PUBLIC_ENV is not set");
|
|
2567
|
-
return { ...appName ? { appName } : {}, workspaceRoot, repoName, serveDomain, env, portOffset };
|
|
2811
|
+
return { ...appName ? { appName } : {}, workspaceRoot, repoName, serveDomain, env, portOffset, workspaceId };
|
|
2812
|
+
}
|
|
2813
|
+
getWorkspaceId({
|
|
2814
|
+
allowEmpty
|
|
2815
|
+
} = {}) {
|
|
2816
|
+
const { workspaceId } = WorkspaceExecutor.getBaseDevEnv();
|
|
2817
|
+
if (!workspaceId && !allowEmpty)
|
|
2818
|
+
throw new Error("Workspace ID is not found");
|
|
2819
|
+
return workspaceId;
|
|
2568
2820
|
}
|
|
2569
2821
|
async scan() {
|
|
2570
2822
|
return await WorkspaceInfo.fromExecutor(this);
|
|
@@ -2768,7 +3020,11 @@ class SysExecutor extends Executor {
|
|
|
2768
3020
|
} = {}) {
|
|
2769
3021
|
if (this.#scanInfo && !refresh)
|
|
2770
3022
|
return this.#scanInfo;
|
|
2771
|
-
const scanInfo = this.type === "app" ? await AppInfo.fromExecutor(this, {
|
|
3023
|
+
const scanInfo = this.type === "app" ? await AppInfo.fromExecutor(this, {
|
|
3024
|
+
refresh
|
|
3025
|
+
}) : await LibInfo.fromExecutor(this, {
|
|
3026
|
+
refresh
|
|
3027
|
+
});
|
|
2772
3028
|
if (write) {
|
|
2773
3029
|
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
2774
3030
|
await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
|
|
@@ -2877,7 +3133,11 @@ class SysExecutor extends Executor {
|
|
|
2877
3133
|
...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
|
|
2878
3134
|
};
|
|
2879
3135
|
const scanInfo = await this.scan();
|
|
2880
|
-
const fileContents = await this._applyTemplate({
|
|
3136
|
+
const fileContents = await this._applyTemplate({
|
|
3137
|
+
...options,
|
|
3138
|
+
scanInfo,
|
|
3139
|
+
dict
|
|
3140
|
+
});
|
|
2881
3141
|
await this.scan();
|
|
2882
3142
|
return fileContents;
|
|
2883
3143
|
}
|
|
@@ -2983,7 +3243,11 @@ class AppExecutor extends SysExecutor {
|
|
|
2983
3243
|
this.#pageKeys = [];
|
|
2984
3244
|
return this.#pageKeys;
|
|
2985
3245
|
}
|
|
2986
|
-
for await (const rel of glob.scan({
|
|
3246
|
+
for await (const rel of glob.scan({
|
|
3247
|
+
cwd: pageDir,
|
|
3248
|
+
absolute: false,
|
|
3249
|
+
onlyFiles: true
|
|
3250
|
+
})) {
|
|
2987
3251
|
const segments = rel.split(path7.sep);
|
|
2988
3252
|
if (segments.some((s) => s === "node_modules"))
|
|
2989
3253
|
continue;
|
|
@@ -2993,7 +3257,10 @@ class AppExecutor extends SysExecutor {
|
|
|
2993
3257
|
if (!isRouteSourceFile(posix))
|
|
2994
3258
|
continue;
|
|
2995
3259
|
const key = `./${posix}`;
|
|
2996
|
-
validateSubRoutePageKey(key, akanConfig2.basePaths, {
|
|
3260
|
+
validateSubRoutePageKey(key, akanConfig2.basePaths, {
|
|
3261
|
+
appName: this.name,
|
|
3262
|
+
filePath: absPath
|
|
3263
|
+
});
|
|
2997
3264
|
const parsed = parseRouteModuleKey(key);
|
|
2998
3265
|
if (parsed.isInternalRootLayout) {
|
|
2999
3266
|
throw new Error(`[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`);
|
|
@@ -3033,7 +3300,11 @@ class AppExecutor extends SysExecutor {
|
|
|
3033
3300
|
]);
|
|
3034
3301
|
}
|
|
3035
3302
|
async scanSync({ refresh = false, write = true } = {}) {
|
|
3036
|
-
const scanInfo = await this.scan({
|
|
3303
|
+
const scanInfo = await this.scan({
|
|
3304
|
+
refresh,
|
|
3305
|
+
write,
|
|
3306
|
+
writeLib: write
|
|
3307
|
+
});
|
|
3037
3308
|
if (write)
|
|
3038
3309
|
await this.syncAssets(scanInfo.getScanResult().libDeps);
|
|
3039
3310
|
return scanInfo;
|
|
@@ -3097,7 +3368,10 @@ class PkgExecutor extends Executor {
|
|
|
3097
3368
|
return scanInfo;
|
|
3098
3369
|
}
|
|
3099
3370
|
async#getDependencyVersion(rootPackageJson, dep) {
|
|
3100
|
-
const rootDeps = {
|
|
3371
|
+
const rootDeps = {
|
|
3372
|
+
...rootPackageJson.dependencies,
|
|
3373
|
+
...rootPackageJson.devDependencies
|
|
3374
|
+
};
|
|
3101
3375
|
const rootVersion = rootDeps[dep];
|
|
3102
3376
|
if (rootVersion)
|
|
3103
3377
|
return rootVersion;
|
|
@@ -3153,7 +3427,14 @@ class PkgExecutor extends Executor {
|
|
|
3153
3427
|
const distPkgJson = {
|
|
3154
3428
|
...pkgJson,
|
|
3155
3429
|
type: "module",
|
|
3156
|
-
exports: {
|
|
3430
|
+
exports: {
|
|
3431
|
+
...pkgJson.exports,
|
|
3432
|
+
".": {
|
|
3433
|
+
import: "./index.ts",
|
|
3434
|
+
types: "./index.ts",
|
|
3435
|
+
default: "./index.ts"
|
|
3436
|
+
}
|
|
3437
|
+
},
|
|
3157
3438
|
engines: { bun: ">=1.3.13" },
|
|
3158
3439
|
...dependencyMaps
|
|
3159
3440
|
};
|
|
@@ -7447,7 +7728,7 @@ class ApplicationBuildRunner {
|
|
|
7447
7728
|
import { cp, mkdir as mkdir8, rm as rm3 } from "fs/promises";
|
|
7448
7729
|
|
|
7449
7730
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
7450
|
-
import { HttpClient, Logger as Logger10 } from "akanjs/common";
|
|
7731
|
+
import { HttpClient as HttpClient2, Logger as Logger10 } from "akanjs/common";
|
|
7451
7732
|
var spinning = (message) => {
|
|
7452
7733
|
const spinner = new Spinner(message, { prefix: message, enableSpin: true }).start();
|
|
7453
7734
|
return spinner;
|
|
@@ -7462,7 +7743,7 @@ var uploadRelease = async (appName, {
|
|
|
7462
7743
|
}) => {
|
|
7463
7744
|
const logger = new Logger10("uploadRelease");
|
|
7464
7745
|
const basePath2 = local ? "http://localhost:8282/backend" : "https://cloud.akanjs.com/backend";
|
|
7465
|
-
const httpClient = new
|
|
7746
|
+
const httpClient = new HttpClient2(basePath2);
|
|
7466
7747
|
const buildPath = `${workspaceRoot}/releases/builds/${appName}-release.tar.gz`;
|
|
7467
7748
|
const appBuildPath = `${workspaceRoot}/releases/builds/${appName}-appBuild.zip`;
|
|
7468
7749
|
const sourcePath = `${workspaceRoot}/releases/sources/${appName}-source.tar.gz`;
|
|
@@ -9449,75 +9730,6 @@ import { Box as Box2, Newline, Text as Text2, useInput as useInput2 } from "ink"
|
|
|
9449
9730
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
9450
9731
|
import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
9451
9732
|
"use client";
|
|
9452
|
-
// pkgs/@akanjs/devkit/cloud/cloudApi.ts
|
|
9453
|
-
class HttpClient2 {
|
|
9454
|
-
baseUrl;
|
|
9455
|
-
constructor(baseUrl) {
|
|
9456
|
-
this.baseUrl = baseUrl;
|
|
9457
|
-
}
|
|
9458
|
-
async get(url, { headers } = {}) {
|
|
9459
|
-
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
9460
|
-
headers: { "Content-Type": "application/json", ...headers }
|
|
9461
|
-
});
|
|
9462
|
-
return response.json();
|
|
9463
|
-
}
|
|
9464
|
-
async post(url, data, { headers } = {}) {
|
|
9465
|
-
const isFormData = data instanceof FormData;
|
|
9466
|
-
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
9467
|
-
method: "POST",
|
|
9468
|
-
body: isFormData ? data : JSON.stringify(data),
|
|
9469
|
-
headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }
|
|
9470
|
-
});
|
|
9471
|
-
return response.json();
|
|
9472
|
-
}
|
|
9473
|
-
}
|
|
9474
|
-
|
|
9475
|
-
class CloudApi {
|
|
9476
|
-
api;
|
|
9477
|
-
#accessToken = null;
|
|
9478
|
-
constructor(host, { accessToken } = {}) {
|
|
9479
|
-
this.api = new HttpClient2(`${host}/api`);
|
|
9480
|
-
this.#accessToken = accessToken ?? null;
|
|
9481
|
-
}
|
|
9482
|
-
async uploadEnv(devProjectId, fileStream) {
|
|
9483
|
-
const formData = new FormData;
|
|
9484
|
-
formData.append("devProjectId", devProjectId);
|
|
9485
|
-
formData.append("fileStream", await new Response(fileStream).blob());
|
|
9486
|
-
const response = await this.api.post(`/uploadEnv/${devProjectId}`, formData);
|
|
9487
|
-
return response.success;
|
|
9488
|
-
}
|
|
9489
|
-
async downloadEnv(devProjectId) {
|
|
9490
|
-
const response = await this.api.get(`/downloadEnv/${devProjectId}`);
|
|
9491
|
-
return response.success;
|
|
9492
|
-
}
|
|
9493
|
-
async getRemoteAuthToken(remoteId) {
|
|
9494
|
-
if (this.#needRefreshToken())
|
|
9495
|
-
return await this.refreshAuthToken();
|
|
9496
|
-
else if (this.#accessToken)
|
|
9497
|
-
return this.#accessToken;
|
|
9498
|
-
const accessToken = await this.api.get(`/getRemoteAuthToken/${remoteId}`);
|
|
9499
|
-
this.#accessToken = {
|
|
9500
|
-
jwt: accessToken.jwt,
|
|
9501
|
-
refreshToken: accessToken.refreshToken,
|
|
9502
|
-
expiresAt: new Date(accessToken.expiresAt)
|
|
9503
|
-
};
|
|
9504
|
-
return accessToken;
|
|
9505
|
-
}
|
|
9506
|
-
async refreshAuthToken() {
|
|
9507
|
-
const response = await this.api.post(`/refreshRemoteAuthToken`, {
|
|
9508
|
-
refreshToken: this.#accessToken?.refreshToken
|
|
9509
|
-
});
|
|
9510
|
-
this.#accessToken = {
|
|
9511
|
-
jwt: response.jwt,
|
|
9512
|
-
refreshToken: response.refreshToken,
|
|
9513
|
-
expiresAt: new Date(response.expiresAt)
|
|
9514
|
-
};
|
|
9515
|
-
return response;
|
|
9516
|
-
}
|
|
9517
|
-
#needRefreshToken() {
|
|
9518
|
-
return !!(this.#accessToken?.expiresAt && this.#accessToken.expiresAt.getTime() < Date.now() - 1000 * 60 * 60);
|
|
9519
|
-
}
|
|
9520
|
-
}
|
|
9521
9733
|
// pkgs/@akanjs/cli/application/application.command.ts
|
|
9522
9734
|
import { select as select6 } from "@inquirer/prompts";
|
|
9523
9735
|
|
|
@@ -10404,7 +10616,7 @@ class PackageScript extends script("package", [PackageRunner]) {
|
|
|
10404
10616
|
|
|
10405
10617
|
// pkgs/@akanjs/cli/cloud/cloud.runner.ts
|
|
10406
10618
|
import path38 from "path";
|
|
10407
|
-
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
10619
|
+
import { confirm as confirm3, input as input5, select as select7 } from "@inquirer/prompts";
|
|
10408
10620
|
import { Logger as Logger15, sleep } from "akanjs/common";
|
|
10409
10621
|
import chalk7 from "chalk";
|
|
10410
10622
|
import * as QRcode from "qrcode";
|
|
@@ -10426,6 +10638,9 @@ async function getLatestPackageVersion(packageName, tag = "latest", registryUrl)
|
|
|
10426
10638
|
}
|
|
10427
10639
|
|
|
10428
10640
|
// pkgs/@akanjs/cli/cloud/cloud.runner.ts
|
|
10641
|
+
var addRemoteEnvServerValue = "__addRemoteEnvServer";
|
|
10642
|
+
var removeRemoteEnvServerValue = "__removeRemoteEnvServer";
|
|
10643
|
+
|
|
10429
10644
|
class CloudRunner extends runner("cloud") {
|
|
10430
10645
|
#akanFrameworkPackages = new Set([
|
|
10431
10646
|
"akanjs",
|
|
@@ -10450,9 +10665,126 @@ class CloudRunner extends runner("cloud") {
|
|
|
10450
10665
|
NPM_CONFIG_REGISTRY: getNpmRegistryUrl(registryUrl)
|
|
10451
10666
|
} : process.env;
|
|
10452
10667
|
}
|
|
10668
|
+
async#addRemoteEnvServer() {
|
|
10669
|
+
const name = (await input5({
|
|
10670
|
+
message: "Remote server name: ",
|
|
10671
|
+
validate: (value) => value.trim() ? true : "Remote server name is required"
|
|
10672
|
+
})).trim();
|
|
10673
|
+
const host = (await input5({
|
|
10674
|
+
message: "Remote server host: ",
|
|
10675
|
+
validate: (value) => value.trim() ? true : "Remote server host is required"
|
|
10676
|
+
})).trim();
|
|
10677
|
+
const username = (await input5({ message: "Remote server username (optional): " })).trim() || undefined;
|
|
10678
|
+
const portInput = (await input5({
|
|
10679
|
+
message: "Remote server SSH port (optional): ",
|
|
10680
|
+
validate: (value) => {
|
|
10681
|
+
const trimmed = value.trim();
|
|
10682
|
+
if (!trimmed)
|
|
10683
|
+
return true;
|
|
10684
|
+
const port = Number(trimmed);
|
|
10685
|
+
return Number.isInteger(port) && port > 0 ? true : "SSH port must be a positive integer";
|
|
10686
|
+
}
|
|
10687
|
+
})).trim();
|
|
10688
|
+
const config = {
|
|
10689
|
+
host,
|
|
10690
|
+
...username ? { username } : {},
|
|
10691
|
+
...portInput ? { port: Number(portInput) } : {}
|
|
10692
|
+
};
|
|
10693
|
+
await GlobalConfig.setRemoteEnvServer(name, config);
|
|
10694
|
+
return { name, config };
|
|
10695
|
+
}
|
|
10696
|
+
async#selectRemoteEnvServer() {
|
|
10697
|
+
const servers = await GlobalConfig.getRemoteEnvServers();
|
|
10698
|
+
const serverEntries = Object.entries(servers).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
|
|
10699
|
+
if (serverEntries.length === 0) {
|
|
10700
|
+
Logger15.info("No remote env servers configured. Add the first remote server for SCP mode.");
|
|
10701
|
+
return await this.#addRemoteEnvServer();
|
|
10702
|
+
}
|
|
10703
|
+
const selectedName = await select7({
|
|
10704
|
+
message: "Select the remote env server",
|
|
10705
|
+
choices: [
|
|
10706
|
+
...serverEntries.map(([name, config2]) => ({
|
|
10707
|
+
name: `${name} (${config2.username ? `${config2.username}@` : ""}${config2.host}${config2.port ? `:${config2.port}` : ""})`,
|
|
10708
|
+
value: name
|
|
10709
|
+
})),
|
|
10710
|
+
{ name: "Add new remote server", value: addRemoteEnvServerValue },
|
|
10711
|
+
{ name: "Remove remote server", value: removeRemoteEnvServerValue }
|
|
10712
|
+
]
|
|
10713
|
+
});
|
|
10714
|
+
if (selectedName === addRemoteEnvServerValue)
|
|
10715
|
+
return await this.#addRemoteEnvServer();
|
|
10716
|
+
if (selectedName === removeRemoteEnvServerValue) {
|
|
10717
|
+
await this.#removeRemoteEnvServer(serverEntries);
|
|
10718
|
+
return await this.#selectRemoteEnvServer();
|
|
10719
|
+
}
|
|
10720
|
+
const config = servers[selectedName];
|
|
10721
|
+
if (!config)
|
|
10722
|
+
throw new Error(`Remote env server is not found: ${selectedName}`);
|
|
10723
|
+
return { name: selectedName, config };
|
|
10724
|
+
}
|
|
10725
|
+
async#removeRemoteEnvServer(serverEntries) {
|
|
10726
|
+
const selectedName = await select7({
|
|
10727
|
+
message: "Select the remote env server to remove",
|
|
10728
|
+
choices: serverEntries.map(([name, config]) => ({
|
|
10729
|
+
name: `${name} (${config.username ? `${config.username}@` : ""}${config.host}${config.port ? `:${config.port}` : ""})`,
|
|
10730
|
+
value: name
|
|
10731
|
+
}))
|
|
10732
|
+
});
|
|
10733
|
+
const shouldRemove = await confirm3({
|
|
10734
|
+
message: `Remove remote env server "${selectedName}"?`,
|
|
10735
|
+
default: false
|
|
10736
|
+
});
|
|
10737
|
+
if (!shouldRemove)
|
|
10738
|
+
return;
|
|
10739
|
+
await GlobalConfig.removeRemoteEnvServer(selectedName);
|
|
10740
|
+
Logger15.info(`Removed remote env server "${selectedName}"`);
|
|
10741
|
+
}
|
|
10742
|
+
async#getRemoteEnvServerWithUsername() {
|
|
10743
|
+
const remoteServer = await this.#selectRemoteEnvServer();
|
|
10744
|
+
if (remoteServer.config.username)
|
|
10745
|
+
return remoteServer;
|
|
10746
|
+
const username = (await input5({
|
|
10747
|
+
message: `SSH username for ${remoteServer.config.host} (optional): `
|
|
10748
|
+
})).trim();
|
|
10749
|
+
return {
|
|
10750
|
+
...remoteServer,
|
|
10751
|
+
config: {
|
|
10752
|
+
...remoteServer.config,
|
|
10753
|
+
...username ? { username } : {}
|
|
10754
|
+
}
|
|
10755
|
+
};
|
|
10756
|
+
}
|
|
10757
|
+
#getRemoteEnvArchivePath() {
|
|
10758
|
+
return `${this.#getRemoteEnvArchiveDir()}/env.tar`;
|
|
10759
|
+
}
|
|
10760
|
+
#getRemoteEnvArchiveDir() {
|
|
10761
|
+
const { repoName } = WorkspaceExecutor.getBaseDevEnv();
|
|
10762
|
+
return `~/secrets/${repoName}`;
|
|
10763
|
+
}
|
|
10764
|
+
#getScpTarget(config, remotePath) {
|
|
10765
|
+
return `${config.username ? `${config.username}@` : ""}${config.host}:${remotePath}`;
|
|
10766
|
+
}
|
|
10767
|
+
#getSshTarget(config) {
|
|
10768
|
+
return `${config.username ? `${config.username}@` : ""}${config.host}`;
|
|
10769
|
+
}
|
|
10770
|
+
#getScpArgs(config, source, target) {
|
|
10771
|
+
return [
|
|
10772
|
+
...config.port ? ["-P", config.port.toString()] : [],
|
|
10773
|
+
source,
|
|
10774
|
+
target
|
|
10775
|
+
];
|
|
10776
|
+
}
|
|
10777
|
+
#getSshArgs(config, command3) {
|
|
10778
|
+
return [
|
|
10779
|
+
...config.port ? ["-p", config.port.toString()] : [],
|
|
10780
|
+
this.#getSshTarget(config),
|
|
10781
|
+
command3
|
|
10782
|
+
];
|
|
10783
|
+
}
|
|
10453
10784
|
async login() {
|
|
10454
|
-
const config = await getHostConfig();
|
|
10455
|
-
const
|
|
10785
|
+
const config = await GlobalConfig.getHostConfig();
|
|
10786
|
+
const cloudApi2 = new CloudApi(config);
|
|
10787
|
+
const self = config.auth ? await cloudApi2.getRemoteSelf() : null;
|
|
10456
10788
|
if (self) {
|
|
10457
10789
|
Logger15.rawLog(chalk7.green(`
|
|
10458
10790
|
\u2713 Already logged in akan cloud as ${self.nickname}
|
|
@@ -10483,11 +10815,12 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
10483
10815
|
Logger15.rawLog(chalk7.dim("Waiting for authentication..."));
|
|
10484
10816
|
const MAX_RETRY = 300;
|
|
10485
10817
|
for (let i = 0;i < MAX_RETRY; i++) {
|
|
10486
|
-
const
|
|
10487
|
-
const
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10818
|
+
const accessToken = await cloudApi2.getRemoteAuthToken(remoteId);
|
|
10819
|
+
const self2 = await cloudApi2.getRemoteSelf();
|
|
10820
|
+
if (accessToken && self2) {
|
|
10821
|
+
await GlobalConfig.setHostConfig(akanCloudHost, {
|
|
10822
|
+
auth: { accessToken, self: self2 }
|
|
10823
|
+
});
|
|
10491
10824
|
Logger15.rawLog(chalk7.green(`\r\u2713 Authentication successful!`));
|
|
10492
10825
|
Logger15.rawLog(chalk7.green.bold(`
|
|
10493
10826
|
\u2728 Welcome aboard, ${self2.nickname}!`));
|
|
@@ -10500,9 +10833,9 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
10500
10833
|
throw new Error(chalk7.red("\u2716 Authentication timed out after 10 minutes. Please try again."));
|
|
10501
10834
|
}
|
|
10502
10835
|
async logout() {
|
|
10503
|
-
const config = await getHostConfig();
|
|
10504
|
-
if (config.auth) {
|
|
10505
|
-
setHostConfig(akanCloudHost, {});
|
|
10836
|
+
const config = await GlobalConfig.getHostConfig();
|
|
10837
|
+
if (config.auth?.self) {
|
|
10838
|
+
await GlobalConfig.setHostConfig(akanCloudHost, {});
|
|
10506
10839
|
Logger15.rawLog(chalk7.magenta.bold(`
|
|
10507
10840
|
\uD83D\uDC4B Goodbye, ${config.auth.self.nickname}!`));
|
|
10508
10841
|
Logger15.rawLog(chalk7.dim(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -10637,20 +10970,78 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
10637
10970
|
}
|
|
10638
10971
|
return normalized;
|
|
10639
10972
|
}
|
|
10640
|
-
async downloadEnv(workspace) {
|
|
10641
|
-
const
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10973
|
+
async downloadEnv(cloudApi2, workspace, workspaceId) {
|
|
10974
|
+
const envArchivePath = "local/env.tar";
|
|
10975
|
+
await workspace.mkdir("local");
|
|
10976
|
+
await workspace.remove(envArchivePath);
|
|
10977
|
+
await cloudApi2.downloadEnv(workspaceId, path38.join(workspace.workspaceRoot, envArchivePath));
|
|
10978
|
+
await workspace.spawn("tar", ["-xf", envArchivePath], {
|
|
10979
|
+
cwd: workspace.workspaceRoot
|
|
10980
|
+
});
|
|
10981
|
+
await workspace.remove(envArchivePath);
|
|
10982
|
+
}
|
|
10983
|
+
async uploadEnv(cloudApi2, workspaceId, filePath) {
|
|
10984
|
+
const file = new File([Bun.file(filePath)], path38.basename(filePath));
|
|
10985
|
+
await cloudApi2.uploadEnv(workspaceId, file);
|
|
10986
|
+
}
|
|
10987
|
+
async downloadEnvByScp(workspace) {
|
|
10988
|
+
const envArchivePath = "local/env.tar";
|
|
10989
|
+
const remoteServer = await this.#getRemoteEnvServerWithUsername();
|
|
10990
|
+
const remoteArchivePath = this.#getRemoteEnvArchivePath();
|
|
10991
|
+
const remoteTarget = this.#getScpTarget(remoteServer.config, remoteArchivePath);
|
|
10992
|
+
await workspace.mkdir("local");
|
|
10993
|
+
await workspace.remove(envArchivePath);
|
|
10994
|
+
try {
|
|
10995
|
+
Logger15.info(`Downloading env archive from remote server "${remoteServer.name}"...`);
|
|
10996
|
+
await workspace.spawn("scp", this.#getScpArgs(remoteServer.config, remoteTarget, envArchivePath), {
|
|
10997
|
+
cwd: workspace.workspaceRoot,
|
|
10998
|
+
stdio: "inherit"
|
|
10999
|
+
});
|
|
11000
|
+
await workspace.spawn("tar", ["-xf", envArchivePath], {
|
|
11001
|
+
cwd: workspace.workspaceRoot
|
|
11002
|
+
});
|
|
11003
|
+
await workspace.remove(envArchivePath);
|
|
11004
|
+
} catch (error) {
|
|
11005
|
+
throw new Error(`Failed to download env archive from remote server "${remoteServer.name}"`, { cause: error });
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
async uploadEnvByScp(workspace, filePath) {
|
|
11009
|
+
const remoteServer = await this.#getRemoteEnvServerWithUsername();
|
|
11010
|
+
const remoteArchiveDir = this.#getRemoteEnvArchiveDir();
|
|
11011
|
+
const remoteArchivePath = this.#getRemoteEnvArchivePath();
|
|
11012
|
+
const remoteTarget = this.#getScpTarget(remoteServer.config, remoteArchivePath);
|
|
11013
|
+
try {
|
|
11014
|
+
await workspace.spawn("ssh", this.#getSshArgs(remoteServer.config, `mkdir -p ${remoteArchiveDir}`), {
|
|
11015
|
+
cwd: workspace.workspaceRoot,
|
|
11016
|
+
stdio: "inherit"
|
|
11017
|
+
});
|
|
11018
|
+
Logger15.info(`Uploading env archive to remote server "${remoteServer.name}"...`);
|
|
11019
|
+
await workspace.spawn("scp", this.#getScpArgs(remoteServer.config, filePath, remoteTarget), {
|
|
11020
|
+
cwd: workspace.workspaceRoot,
|
|
11021
|
+
stdio: "inherit"
|
|
11022
|
+
});
|
|
11023
|
+
} catch (error) {
|
|
11024
|
+
throw new Error(`Failed to upload env archive to remote server "${remoteServer.name}"`, { cause: error });
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
async gatherEnvFiles(workspace) {
|
|
11028
|
+
const envFilePattern = /^env\.(client|server)\.(?!(type|example)\.ts$).+\.ts$/;
|
|
11029
|
+
const [appNames, libNames] = await workspace.getExecs();
|
|
11030
|
+
const envDirs = [
|
|
11031
|
+
...appNames.map((appName) => `apps/${appName}/env`),
|
|
11032
|
+
...libNames.map((libName) => `libs/${libName}/env`)
|
|
11033
|
+
];
|
|
11034
|
+
const envFilePaths = (await Promise.all(envDirs.map(async (envDir) => (await workspace.readdir(envDir)).filter((fileName) => envFilePattern.test(fileName)).map((fileName) => `${envDir}/${fileName}`)))).flat().sort();
|
|
11035
|
+
await workspace.mkdir("local");
|
|
11036
|
+
await workspace.remove("local/env.tar");
|
|
11037
|
+
if (envFilePaths.length === 0)
|
|
11038
|
+
throw new Error("No environment files found to archive");
|
|
11039
|
+
await workspace.spawn("tar", ["-cf", "local/env.tar", ...envFilePaths], {
|
|
11040
|
+
cwd: workspace.workspaceRoot
|
|
10648
11041
|
});
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
Logger15.info(`Environment variables: ${JSON.stringify(env.env, null, 2)}`);
|
|
11042
|
+
Logger15.info(`Archived ${envFilePaths.length} environment files to local/env.tar`);
|
|
11043
|
+
return { files: envFilePaths, path: "local/env.tar" };
|
|
10652
11044
|
}
|
|
10653
|
-
async uploadEnv(workspace) {}
|
|
10654
11045
|
}
|
|
10655
11046
|
|
|
10656
11047
|
// pkgs/@akanjs/cli/cloud/cloud.script.ts
|
|
@@ -10676,10 +11067,23 @@ class CloudScript extends script("cloud", [
|
|
|
10676
11067
|
await session.ask(question);
|
|
10677
11068
|
}
|
|
10678
11069
|
async downloadEnv(workspace) {
|
|
10679
|
-
|
|
11070
|
+
const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
|
|
11071
|
+
if (workspaceId) {
|
|
11072
|
+
const cloudApi2 = await CloudApi.fromHost();
|
|
11073
|
+
await this.cloudRunner.downloadEnv(cloudApi2, workspace, workspaceId);
|
|
11074
|
+
return;
|
|
11075
|
+
}
|
|
11076
|
+
await this.cloudRunner.downloadEnvByScp(workspace);
|
|
10680
11077
|
}
|
|
10681
11078
|
async uploadEnv(workspace) {
|
|
10682
|
-
|
|
11079
|
+
const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
|
|
11080
|
+
const { path: path39 } = await this.cloudRunner.gatherEnvFiles(workspace);
|
|
11081
|
+
if (workspaceId) {
|
|
11082
|
+
const cloudApi2 = await CloudApi.fromHost();
|
|
11083
|
+
await this.cloudRunner.uploadEnv(cloudApi2, workspaceId, path39);
|
|
11084
|
+
return;
|
|
11085
|
+
}
|
|
11086
|
+
await this.cloudRunner.uploadEnvByScp(workspace, path39);
|
|
10683
11087
|
}
|
|
10684
11088
|
async deployAkan(workspace, { test = true, registryUrl } = {}) {
|
|
10685
11089
|
const akanPkgs = await this.cloudRunner.getAkanPkgs(workspace);
|
|
@@ -10718,7 +11122,10 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
|
|
|
10718
11122
|
resetLlm: target({ desc: "Reset LLM configuration to default" }).with(Workspace).exec(function(workspace) {
|
|
10719
11123
|
this.cloudScript.resetLlm(workspace);
|
|
10720
11124
|
}),
|
|
10721
|
-
ask: target({
|
|
11125
|
+
ask: target({
|
|
11126
|
+
devOnly: true,
|
|
11127
|
+
desc: "Ask AI assistant a question about your project"
|
|
11128
|
+
}).option("question", String, { ask: "question to ask" }).with(Workspace).exec(async function(question, workspace) {
|
|
10722
11129
|
await this.cloudScript.ask(question, workspace);
|
|
10723
11130
|
}),
|
|
10724
11131
|
deployAkan: target({
|
|
@@ -10753,10 +11160,14 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
|
|
|
10753
11160
|
registryUrl: resolveRegistryUrl(registry)
|
|
10754
11161
|
});
|
|
10755
11162
|
}),
|
|
10756
|
-
downloadEnv: target({
|
|
11163
|
+
downloadEnv: target({
|
|
11164
|
+
desc: "Download environment variables from cloud or SCP server"
|
|
11165
|
+
}).with(Workspace).exec(async function(workspace) {
|
|
10757
11166
|
await this.cloudScript.downloadEnv(workspace);
|
|
10758
11167
|
}),
|
|
10759
|
-
uploadEnv: target({
|
|
11168
|
+
uploadEnv: target({
|
|
11169
|
+
desc: "Upload environment variables to cloud or SCP server"
|
|
11170
|
+
}).with(Workspace).exec(async function(workspace) {
|
|
10760
11171
|
await this.cloudScript.uploadEnv(workspace);
|
|
10761
11172
|
})
|
|
10762
11173
|
})) {
|
|
@@ -11230,7 +11641,7 @@ class LocalRegistryCommand extends command("local-registry", [LocalRegistryScrip
|
|
|
11230
11641
|
import { lowerlize } from "akanjs/common";
|
|
11231
11642
|
|
|
11232
11643
|
// pkgs/@akanjs/cli/module/module.script.ts
|
|
11233
|
-
import { input as
|
|
11644
|
+
import { input as input6 } from "@inquirer/prompts";
|
|
11234
11645
|
import { capitalize as capitalize6, randomPicks as randomPicks2 } from "akanjs/common";
|
|
11235
11646
|
|
|
11236
11647
|
// pkgs/@akanjs/cli/page/page.runner.ts
|
|
@@ -11657,8 +12068,8 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
11657
12068
|
const { constant, dictionary } = await this.moduleRunner.createModuleTemplate(executor);
|
|
11658
12069
|
if (page && sys3.type === "app")
|
|
11659
12070
|
await this.pageScript.createCrudPage(executor, { app: sys3, basePath: null, single: false });
|
|
11660
|
-
const modelDesc = description ?? await
|
|
11661
|
-
const modelSchemaDesign = schemaDescription ?? await
|
|
12071
|
+
const modelDesc = description ?? await input6({ message: "description of module" });
|
|
12072
|
+
const modelSchemaDesign = schemaDescription ?? await input6({ message: "schema description of module" });
|
|
11662
12073
|
const config = await sys3.getConfig();
|
|
11663
12074
|
const moduleRequest = new ModuleRequest({ sysType: sys3.type, sysName: sys3.name, modelName: name, config });
|
|
11664
12075
|
const constantRequestPrompt = await moduleRequest.requestModelConstant({
|
|
@@ -11829,7 +12240,7 @@ class PageCommand extends command("page", [PageScript], ({ public: target }) =>
|
|
|
11829
12240
|
import { lowerlize as lowerlize2 } from "akanjs/common";
|
|
11830
12241
|
|
|
11831
12242
|
// pkgs/@akanjs/cli/scalar/scalar.prompt.ts
|
|
11832
|
-
import { input as
|
|
12243
|
+
import { input as input7 } from "@inquirer/prompts";
|
|
11833
12244
|
class ScalarPrompt extends Prompter {
|
|
11834
12245
|
sys;
|
|
11835
12246
|
name;
|
|
@@ -11839,13 +12250,13 @@ class ScalarPrompt extends Prompter {
|
|
|
11839
12250
|
this.name = name;
|
|
11840
12251
|
}
|
|
11841
12252
|
async requestUpdateConstant() {
|
|
11842
|
-
const request = await
|
|
12253
|
+
const request = await input7({ message: `What do you want to change?` });
|
|
11843
12254
|
return { request, validate: undefined };
|
|
11844
12255
|
}
|
|
11845
12256
|
async requestCreateConstant() {
|
|
11846
12257
|
const constantFiles = await this.sys.getConstantFilesWithLibs();
|
|
11847
|
-
const description = await
|
|
11848
|
-
const schemaDescription = await
|
|
12258
|
+
const description = await input7({ message: "description of scalar" });
|
|
12259
|
+
const schemaDescription = await input7({ message: "schema description of scalar" });
|
|
11849
12260
|
await this.sys.applyTemplate({
|
|
11850
12261
|
basePath: "./lib/__scalar",
|
|
11851
12262
|
template: "__scalar",
|