@akanjs/cli 2.1.1-rc.2 → 2.1.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guidelines/modelConstant/modelConstant.generate.json +1 -0
- package/guidelines/modelDictionary/modelDictionary.generate.json +1 -0
- package/guidelines/scalarConstant/scalarConstant.generate.json +1 -0
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +1 -0
- package/incrementalBuilder.proc.js +447 -134
- package/index.js +614 -256
- package/package.json +2 -2
- package/templates/lib/dict.ts +5 -2
- package/templates/lib/useClient.ts +6 -2
- package/templates/module/__Model__.Unit.tsx +5 -2
- package/typecheck.proc.js +19 -0
|
@@ -205,11 +205,13 @@ class HttpClient {
|
|
|
205
205
|
class CloudApi {
|
|
206
206
|
#api;
|
|
207
207
|
#accessToken = null;
|
|
208
|
-
|
|
208
|
+
#workspace;
|
|
209
|
+
static async fromHost(workspace, host) {
|
|
209
210
|
const hostConfig = await GlobalConfig.getHostConfig(host);
|
|
210
|
-
return new CloudApi(hostConfig);
|
|
211
|
+
return new CloudApi(workspace, hostConfig);
|
|
211
212
|
}
|
|
212
|
-
constructor(hostConfig) {
|
|
213
|
+
constructor(workspace, hostConfig) {
|
|
214
|
+
this.#workspace = workspace;
|
|
213
215
|
const host = akanCloudHost;
|
|
214
216
|
this.#api = new HttpClient(`${host}/api`);
|
|
215
217
|
this.#accessToken = hostConfig.auth?.accessToken ?? null;
|
|
@@ -225,16 +227,17 @@ class CloudApi {
|
|
|
225
227
|
const data = await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
|
|
226
228
|
return data;
|
|
227
229
|
}
|
|
228
|
-
async downloadEnv(devProjectId
|
|
230
|
+
async downloadEnv(devProjectId) {
|
|
231
|
+
const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
|
|
229
232
|
await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
|
|
230
233
|
}
|
|
231
234
|
async getRemoteAuthToken(remoteId) {
|
|
232
235
|
try {
|
|
233
236
|
if (this.#accessToken) {
|
|
234
237
|
if (GlobalConfig.needRefreshToken(this.#accessToken))
|
|
235
|
-
return await this
|
|
238
|
+
return await this.#refreshAuthToken();
|
|
236
239
|
else
|
|
237
|
-
return await this
|
|
240
|
+
return await this.#refreshAuthToken();
|
|
238
241
|
}
|
|
239
242
|
const accessToken = await this.#api.get(`/getRemoteAuthToken/${remoteId}`);
|
|
240
243
|
this.#accessToken = GlobalConfig.toAccessToken(accessToken);
|
|
@@ -246,10 +249,14 @@ class CloudApi {
|
|
|
246
249
|
return null;
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
|
-
async
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
async#refreshAuthToken() {
|
|
253
|
+
const refreshToken = this.#accessToken?.refreshToken;
|
|
254
|
+
if (!refreshToken)
|
|
255
|
+
throw new Error("No refresh token");
|
|
256
|
+
return await this.refreshAuthToken(refreshToken);
|
|
257
|
+
}
|
|
258
|
+
async refreshAuthToken(refreshToken) {
|
|
259
|
+
const response = await this.#api.post(`/refreshRemoteAuthToken`, { refreshToken });
|
|
253
260
|
this.#accessToken = GlobalConfig.toAccessToken(response);
|
|
254
261
|
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
255
262
|
return this.#accessToken;
|
|
@@ -329,6 +336,31 @@ var supportedLlmModels = [
|
|
|
329
336
|
"deepseek-chat",
|
|
330
337
|
"deepseek-reasoner"
|
|
331
338
|
];
|
|
339
|
+
var parseTypescriptFileBlocks = (text) => {
|
|
340
|
+
const fileBlocks = [];
|
|
341
|
+
const codeBlockRegex = /```(?:typescript|ts|tsx)\s*\n([\s\S]*?)```/gi;
|
|
342
|
+
const filePathRegex = /^\s*\/\/\s*File:\s*(.+?)\s*$/im;
|
|
343
|
+
for (const codeBlock of text.matchAll(codeBlockRegex)) {
|
|
344
|
+
const content = codeBlock[1]?.trim();
|
|
345
|
+
if (!content)
|
|
346
|
+
continue;
|
|
347
|
+
const filePath = filePathRegex.exec(content)?.[1]?.trim();
|
|
348
|
+
if (!filePath)
|
|
349
|
+
continue;
|
|
350
|
+
fileBlocks.push({
|
|
351
|
+
filePath,
|
|
352
|
+
content: content.replace(filePathRegex, "").trim()
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return fileBlocks;
|
|
356
|
+
};
|
|
357
|
+
var preserveTypescriptResponseContent = (previousContent, nextContent) => {
|
|
358
|
+
const previousWrites = parseTypescriptFileBlocks(previousContent);
|
|
359
|
+
const nextWrites = parseTypescriptFileBlocks(nextContent);
|
|
360
|
+
if (previousWrites.length > 0 && nextWrites.length === 0)
|
|
361
|
+
return previousContent;
|
|
362
|
+
return nextContent;
|
|
363
|
+
};
|
|
332
364
|
|
|
333
365
|
class AiSession {
|
|
334
366
|
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
@@ -448,7 +480,7 @@ class AiSession {
|
|
|
448
480
|
const humanMessage = new HumanMessage(question);
|
|
449
481
|
this.messageHistory.push(humanMessage);
|
|
450
482
|
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
451
|
-
let reasoningResponse = "", fullResponse = ""
|
|
483
|
+
let reasoningResponse = "", fullResponse = "";
|
|
452
484
|
for await (const chunk of stream) {
|
|
453
485
|
if (loader.isSpinning())
|
|
454
486
|
loader.succeed(`${AiSession.#chat.model} responded`);
|
|
@@ -469,7 +501,6 @@ class AiSession {
|
|
|
469
501
|
fullResponse += content;
|
|
470
502
|
onChunk(content);
|
|
471
503
|
}
|
|
472
|
-
tokenIdx++;
|
|
473
504
|
}
|
|
474
505
|
fullResponse += `
|
|
475
506
|
`;
|
|
@@ -477,7 +508,7 @@ class AiSession {
|
|
|
477
508
|
`);
|
|
478
509
|
this.messageHistory.push(new AIMessage(fullResponse));
|
|
479
510
|
return { content: fullResponse, messageHistory: this.messageHistory };
|
|
480
|
-
} catch
|
|
511
|
+
} catch {
|
|
481
512
|
loader.fail(`${AiSession.#chat.model} failed to respond`);
|
|
482
513
|
throw new Error("Failed to stream response");
|
|
483
514
|
}
|
|
@@ -487,7 +518,8 @@ class AiSession {
|
|
|
487
518
|
onReasoning,
|
|
488
519
|
maxTry = MAX_ASK_TRY,
|
|
489
520
|
validate,
|
|
490
|
-
approve
|
|
521
|
+
approve,
|
|
522
|
+
fallbackToPreviousTypescript
|
|
491
523
|
} = {}) {
|
|
492
524
|
for (let tryCount = 0;tryCount < maxTry; tryCount++) {
|
|
493
525
|
let response = await this.ask(question, { onChunk, onReasoning });
|
|
@@ -495,7 +527,14 @@ class AiSession {
|
|
|
495
527
|
const validateQuestion = `Double check if the response meets the requirements and conditions, and follow the instructions. If not, rewrite it.
|
|
496
528
|
${validate.map((v) => `- ${v}`).join(`
|
|
497
529
|
`)}`;
|
|
498
|
-
|
|
530
|
+
const validateResponse = await this.ask(validateQuestion, {
|
|
531
|
+
onChunk,
|
|
532
|
+
onReasoning
|
|
533
|
+
});
|
|
534
|
+
response = {
|
|
535
|
+
...validateResponse,
|
|
536
|
+
content: fallbackToPreviousTypescript ? preserveTypescriptResponseContent(response.content, validateResponse.content) : validateResponse.content
|
|
537
|
+
};
|
|
499
538
|
}
|
|
500
539
|
const isConfirmed = approve ? true : await select({
|
|
501
540
|
message: "Do you want to edit the response?",
|
|
@@ -528,15 +567,28 @@ ${validate.map((v) => `- ${v}`).join(`
|
|
|
528
567
|
return this;
|
|
529
568
|
}
|
|
530
569
|
async writeTypescripts(question, executor, options = {}) {
|
|
531
|
-
const content = await this.edit(question,
|
|
570
|
+
const content = await this.edit(question, {
|
|
571
|
+
...options,
|
|
572
|
+
fallbackToPreviousTypescript: true
|
|
573
|
+
});
|
|
532
574
|
const writes = this.#getTypescriptCodes(content);
|
|
575
|
+
if (!writes.length)
|
|
576
|
+
throw new Error("No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.");
|
|
533
577
|
for (const write of writes)
|
|
534
578
|
await executor.writeFile(write.filePath, write.content);
|
|
535
579
|
return await this.#tryFixTypescripts(writes, executor, options);
|
|
536
580
|
}
|
|
537
|
-
async#editTypescripts(question, options = {}) {
|
|
538
|
-
const content = await this.edit(question,
|
|
539
|
-
|
|
581
|
+
async#editTypescripts(question, options = {}, fallbackWrites) {
|
|
582
|
+
const content = await this.edit(question, {
|
|
583
|
+
...options,
|
|
584
|
+
fallbackToPreviousTypescript: true
|
|
585
|
+
});
|
|
586
|
+
const writes = this.#getTypescriptCodes(content);
|
|
587
|
+
if (!writes.length && fallbackWrites?.length)
|
|
588
|
+
return fallbackWrites;
|
|
589
|
+
if (!writes.length)
|
|
590
|
+
throw new Error("No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.");
|
|
591
|
+
return writes;
|
|
540
592
|
}
|
|
541
593
|
async#tryFixTypescripts(writes, executor, options = {}) {
|
|
542
594
|
const MAX_EDIT_TRY = 5;
|
|
@@ -545,13 +597,15 @@ ${validate.map((v) => `- ${v}`).join(`
|
|
|
545
597
|
prefix: `\uD83E\uDD16akan-editor`
|
|
546
598
|
}).start();
|
|
547
599
|
const fileChecks = await Promise.all(writes.map(async ({ filePath }) => {
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
600
|
+
const lintResult = await executor.lint(filePath, { fix: true });
|
|
601
|
+
const typeCheckResult = await executor.typeCheckAsync(filePath);
|
|
602
|
+
const hasTypeErrors = typeCheckResult.fileErrors.length > 0;
|
|
603
|
+
const hasLintErrors = lintResult.errors.length > 0;
|
|
604
|
+
const needFix = hasTypeErrors || hasLintErrors;
|
|
605
|
+
return { filePath, typeCheckResult, lintResult, needFix };
|
|
552
606
|
}));
|
|
553
|
-
const
|
|
554
|
-
if (
|
|
607
|
+
const hasAnyFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
608
|
+
if (hasAnyFix) {
|
|
555
609
|
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
556
610
|
fileChecks.forEach((fileCheck) => {
|
|
557
611
|
Logger2.rawLog(`TypeCheck Result
|
|
@@ -567,7 +621,7 @@ ${fileCheck.lintResult.message}`);
|
|
|
567
621
|
...options,
|
|
568
622
|
validate: undefined,
|
|
569
623
|
approve: true
|
|
570
|
-
});
|
|
624
|
+
}, writes);
|
|
571
625
|
for (const write of writes)
|
|
572
626
|
await executor.writeFile(write.filePath, write.content);
|
|
573
627
|
} else {
|
|
@@ -578,21 +632,7 @@ ${fileCheck.lintResult.message}`);
|
|
|
578
632
|
throw new Error("Failed to create scalar");
|
|
579
633
|
}
|
|
580
634
|
#getTypescriptCodes(text) {
|
|
581
|
-
|
|
582
|
-
if (!codes)
|
|
583
|
-
return [];
|
|
584
|
-
const result = codes.map((code) => {
|
|
585
|
-
const content = /```(typescript|tsx)([\s\S]*?)```/.exec(code)?.[2];
|
|
586
|
-
if (!content)
|
|
587
|
-
return null;
|
|
588
|
-
const filePath = /\/\/ File: (.*?)(?:\n|$)/.exec(content)?.[1]?.trim();
|
|
589
|
-
if (!filePath)
|
|
590
|
-
return null;
|
|
591
|
-
const contentWithoutFilepath = content.replace(`// File: ${filePath}
|
|
592
|
-
`, "").trim();
|
|
593
|
-
return { filePath, content: contentWithoutFilepath };
|
|
594
|
-
});
|
|
595
|
-
return result.filter((code) => code !== null);
|
|
635
|
+
return parseTypescriptFileBlocks(text);
|
|
596
636
|
}
|
|
597
637
|
async editMarkdown(request, options = {}) {
|
|
598
638
|
const content = await this.edit(request, options);
|
|
@@ -610,13 +650,13 @@ ${fileCheck.lintResult.message}`);
|
|
|
610
650
|
}
|
|
611
651
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
612
652
|
import path9 from "path";
|
|
613
|
-
import { Logger as
|
|
653
|
+
import { Logger as Logger5 } from "akanjs/common";
|
|
614
654
|
|
|
615
655
|
// pkgs/@akanjs/devkit/executors.ts
|
|
616
656
|
import {
|
|
617
657
|
exec,
|
|
618
658
|
fork,
|
|
619
|
-
spawn
|
|
659
|
+
spawn as spawn2
|
|
620
660
|
} from "child_process";
|
|
621
661
|
import { readFileSync as readFileSync3 } from "fs";
|
|
622
662
|
import { copyFile, mkdir as mkdir2, readdir as readDirEntries, stat as stat2 } from "fs/promises";
|
|
@@ -624,7 +664,7 @@ import path7 from "path";
|
|
|
624
664
|
import {
|
|
625
665
|
capitalize,
|
|
626
666
|
isRouteSourceFile,
|
|
627
|
-
Logger as
|
|
667
|
+
Logger as Logger3,
|
|
628
668
|
parseRouteModuleKey,
|
|
629
669
|
validatePageSourceFile,
|
|
630
670
|
validateSubRoutePageKey
|
|
@@ -671,8 +711,18 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
|
|
|
671
711
|
"mui-core",
|
|
672
712
|
"react-icons/*"
|
|
673
713
|
];
|
|
674
|
-
var WORKSPACE_BARREL_FACETS = [
|
|
675
|
-
|
|
714
|
+
var WORKSPACE_BARREL_FACETS = [
|
|
715
|
+
"ui",
|
|
716
|
+
"webkit",
|
|
717
|
+
"common",
|
|
718
|
+
"client",
|
|
719
|
+
"server"
|
|
720
|
+
];
|
|
721
|
+
var SSR_RUNTIME_PACKAGES = [
|
|
722
|
+
"react",
|
|
723
|
+
"react-dom",
|
|
724
|
+
"react-server-dom-webpack"
|
|
725
|
+
];
|
|
676
726
|
var NATIVE_RUNTIME_PACKAGES = ["sharp"];
|
|
677
727
|
var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
|
|
678
728
|
var DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
@@ -732,7 +782,12 @@ class AkanAppConfig {
|
|
|
732
782
|
...libs.flatMap((lib) => WORKSPACE_BARREL_FACETS.map((facet) => `@libs/${lib}/${facet}`)),
|
|
733
783
|
...config?.barrelImports ?? []
|
|
734
784
|
];
|
|
735
|
-
this.optimizeImports = [
|
|
785
|
+
this.optimizeImports = [
|
|
786
|
+
...new Set([
|
|
787
|
+
...DEFAULT_OPTIMIZE_IMPORTS,
|
|
788
|
+
...config?.optimizeImports ?? []
|
|
789
|
+
])
|
|
790
|
+
];
|
|
736
791
|
this.images = mergeImageConfig(config?.images);
|
|
737
792
|
this.i18n = resolveAkanI18nConfig(config?.i18n);
|
|
738
793
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
@@ -843,7 +898,13 @@ class AkanAppConfig {
|
|
|
843
898
|
}
|
|
844
899
|
#makeDockerContent(docker) {
|
|
845
900
|
if (docker.content)
|
|
846
|
-
return {
|
|
901
|
+
return {
|
|
902
|
+
content: docker.content,
|
|
903
|
+
image: {},
|
|
904
|
+
preRuns: [],
|
|
905
|
+
postRuns: [],
|
|
906
|
+
command: []
|
|
907
|
+
};
|
|
847
908
|
const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
|
|
848
909
|
const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
|
|
849
910
|
const imageScript = docker.image ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim") : "FROM oven/bun:1-slim";
|
|
@@ -874,7 +935,13 @@ ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
|
|
|
874
935
|
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
875
936
|
|
|
876
937
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
877
|
-
return {
|
|
938
|
+
return {
|
|
939
|
+
content,
|
|
940
|
+
image: imageScript,
|
|
941
|
+
preRuns: docker.preRuns ?? [],
|
|
942
|
+
postRuns: docker.postRuns ?? [],
|
|
943
|
+
command
|
|
944
|
+
};
|
|
878
945
|
}
|
|
879
946
|
static async from(app) {
|
|
880
947
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
@@ -900,9 +967,24 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
900
967
|
...SSR_RUNTIME_PACKAGES,
|
|
901
968
|
...NATIVE_RUNTIME_PACKAGES,
|
|
902
969
|
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
903
|
-
...
|
|
970
|
+
...this.getDatabaseModeRuntimePackages()
|
|
904
971
|
];
|
|
905
972
|
}
|
|
973
|
+
getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
|
|
974
|
+
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
975
|
+
}
|
|
976
|
+
getMissingDatabaseModeDependencySpecs(databaseMode = this.defaultDatabaseMode) {
|
|
977
|
+
const rootDependencies = {
|
|
978
|
+
...this.rootPackageJson.dependencies,
|
|
979
|
+
...this.rootPackageJson.devDependencies
|
|
980
|
+
};
|
|
981
|
+
return this.getDatabaseModeRuntimePackages(databaseMode).filter((lib) => !rootDependencies[lib]).map((lib) => {
|
|
982
|
+
const version = this.#resolveProductionDependencyVersion(lib);
|
|
983
|
+
if (!version)
|
|
984
|
+
throw new Error(`Dependency ${lib} not found in package.json`);
|
|
985
|
+
return `${lib}@${version}`;
|
|
986
|
+
});
|
|
987
|
+
}
|
|
906
988
|
getProductionPackageJson(data = {}) {
|
|
907
989
|
return {
|
|
908
990
|
name: this.app.name,
|
|
@@ -938,7 +1020,12 @@ function getAkanPackageJson() {
|
|
|
938
1020
|
return akanPackageJson;
|
|
939
1021
|
} catch {}
|
|
940
1022
|
}
|
|
941
|
-
akanPackageJson = {
|
|
1023
|
+
akanPackageJson = {
|
|
1024
|
+
name: "akanjs",
|
|
1025
|
+
version: "0.0.0",
|
|
1026
|
+
description: "akanjs",
|
|
1027
|
+
dependencies: {}
|
|
1028
|
+
};
|
|
942
1029
|
return akanPackageJson;
|
|
943
1030
|
}
|
|
944
1031
|
function mergeImageConfig(config = {}) {
|
|
@@ -962,7 +1049,9 @@ class AkanLibConfig {
|
|
|
962
1049
|
this.externalLibs = config?.externalLibs ?? [];
|
|
963
1050
|
}
|
|
964
1051
|
static async from(lib) {
|
|
965
|
-
const [configImp] = await Promise.all([
|
|
1052
|
+
const [configImp] = await Promise.all([
|
|
1053
|
+
import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)
|
|
1054
|
+
]);
|
|
966
1055
|
const config = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
967
1056
|
return new AkanLibConfig(lib, config);
|
|
968
1057
|
}
|
|
@@ -987,23 +1076,146 @@ import path2 from "path";
|
|
|
987
1076
|
var getDirname = (url) => path2.dirname(new URL(url).pathname);
|
|
988
1077
|
|
|
989
1078
|
// pkgs/@akanjs/devkit/linter.ts
|
|
1079
|
+
import { spawn } from "child_process";
|
|
990
1080
|
import { existsSync, readFileSync } from "fs";
|
|
991
|
-
import
|
|
992
|
-
import { Logger as Logger3 } from "akanjs/common";
|
|
1081
|
+
import path3 from "path";
|
|
993
1082
|
import chalk2 from "chalk";
|
|
994
1083
|
|
|
995
1084
|
class Linter {
|
|
996
|
-
#logger = new Logger3("Linter");
|
|
997
1085
|
lintRoot;
|
|
1086
|
+
#biomeBin;
|
|
998
1087
|
constructor(cwdPath) {
|
|
999
|
-
this.lintRoot = this.#
|
|
1088
|
+
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
1089
|
+
const localBiomeBin = path3.join(this.lintRoot, "node_modules/.bin/biome");
|
|
1090
|
+
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
1000
1091
|
}
|
|
1001
|
-
#
|
|
1002
|
-
const configPath2 = path3.join(dir, "
|
|
1092
|
+
#findBiomeRootPath(dir) {
|
|
1093
|
+
const configPath2 = path3.join(dir, "biome.json");
|
|
1003
1094
|
if (existsSync(configPath2))
|
|
1004
1095
|
return dir;
|
|
1005
1096
|
const parentDir = path3.dirname(dir);
|
|
1006
|
-
|
|
1097
|
+
if (parentDir === dir)
|
|
1098
|
+
throw new Error(`biome.json not found from ${dir}`);
|
|
1099
|
+
return this.#findBiomeRootPath(parentDir);
|
|
1100
|
+
}
|
|
1101
|
+
#toBiomePath(filePath) {
|
|
1102
|
+
const relativePath = path3.relative(this.lintRoot, filePath);
|
|
1103
|
+
if (!relativePath.startsWith("..") && !path3.isAbsolute(relativePath))
|
|
1104
|
+
return relativePath;
|
|
1105
|
+
return filePath;
|
|
1106
|
+
}
|
|
1107
|
+
#resolveFilePath(filePath) {
|
|
1108
|
+
return path3.isAbsolute(filePath) ? filePath : path3.join(this.lintRoot, filePath);
|
|
1109
|
+
}
|
|
1110
|
+
async#runBiome(args, input2) {
|
|
1111
|
+
return await new Promise((resolve, reject) => {
|
|
1112
|
+
const proc = spawn(this.#biomeBin, args, {
|
|
1113
|
+
cwd: this.lintRoot,
|
|
1114
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1115
|
+
});
|
|
1116
|
+
let stdout = "";
|
|
1117
|
+
let stderr = "";
|
|
1118
|
+
proc.stdout.on("data", (data) => {
|
|
1119
|
+
stdout += data.toString();
|
|
1120
|
+
});
|
|
1121
|
+
proc.stderr.on("data", (data) => {
|
|
1122
|
+
stderr += data.toString();
|
|
1123
|
+
});
|
|
1124
|
+
proc.on("error", reject);
|
|
1125
|
+
proc.on("close", (code) => resolve({ stdout, stderr, code }));
|
|
1126
|
+
proc.stdin.end(input2);
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
#parseBiomeReport(output) {
|
|
1130
|
+
const jsonStart = output.indexOf("{");
|
|
1131
|
+
const jsonEnd = output.lastIndexOf("}");
|
|
1132
|
+
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
|
|
1133
|
+
throw new Error(output.trim() || "No Biome JSON output");
|
|
1134
|
+
return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
|
|
1135
|
+
}
|
|
1136
|
+
#diagnosticFilePath(diagnostic, fallbackFilePath) {
|
|
1137
|
+
const diagnosticPath = diagnostic.location?.path;
|
|
1138
|
+
if (!diagnosticPath)
|
|
1139
|
+
return fallbackFilePath;
|
|
1140
|
+
return path3.isAbsolute(diagnosticPath) ? diagnosticPath : path3.join(this.lintRoot, diagnosticPath);
|
|
1141
|
+
}
|
|
1142
|
+
#createLintMessage(diagnostic) {
|
|
1143
|
+
const start = diagnostic.location?.start;
|
|
1144
|
+
const end = diagnostic.location?.end;
|
|
1145
|
+
return {
|
|
1146
|
+
line: Math.max(1, start?.line ?? 1),
|
|
1147
|
+
column: Math.max(1, start?.column ?? 1),
|
|
1148
|
+
endLine: end?.line,
|
|
1149
|
+
endColumn: end?.column,
|
|
1150
|
+
message: diagnostic.message,
|
|
1151
|
+
ruleId: diagnostic.category ?? null,
|
|
1152
|
+
severity: diagnostic.severity === "error" ? 2 : 1
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
#toLintResults(report, filePath) {
|
|
1156
|
+
const resultsByPath = new Map;
|
|
1157
|
+
for (const diagnostic of report.diagnostics ?? []) {
|
|
1158
|
+
if (diagnostic.severity !== "error" && diagnostic.severity !== "warning")
|
|
1159
|
+
continue;
|
|
1160
|
+
const diagnosticFilePath = this.#diagnosticFilePath(diagnostic, filePath);
|
|
1161
|
+
const result = resultsByPath.get(diagnosticFilePath) ?? {
|
|
1162
|
+
filePath: diagnosticFilePath,
|
|
1163
|
+
messages: [],
|
|
1164
|
+
errorCount: 0,
|
|
1165
|
+
warningCount: 0,
|
|
1166
|
+
fixableErrorCount: 0,
|
|
1167
|
+
fixableWarningCount: 0
|
|
1168
|
+
};
|
|
1169
|
+
const message = this.#createLintMessage(diagnostic);
|
|
1170
|
+
result.messages.push(message);
|
|
1171
|
+
if (message.severity === 2)
|
|
1172
|
+
result.errorCount += 1;
|
|
1173
|
+
else
|
|
1174
|
+
result.warningCount += 1;
|
|
1175
|
+
resultsByPath.set(diagnosticFilePath, result);
|
|
1176
|
+
}
|
|
1177
|
+
return [
|
|
1178
|
+
resultsByPath.get(filePath) ?? {
|
|
1179
|
+
filePath,
|
|
1180
|
+
messages: [],
|
|
1181
|
+
errorCount: 0,
|
|
1182
|
+
warningCount: 0,
|
|
1183
|
+
fixableErrorCount: 0,
|
|
1184
|
+
fixableWarningCount: 0
|
|
1185
|
+
},
|
|
1186
|
+
...[...resultsByPath.entries()].filter(([resultPath]) => resultPath !== filePath).map(([, result]) => result)
|
|
1187
|
+
];
|
|
1188
|
+
}
|
|
1189
|
+
#splitMessages(results) {
|
|
1190
|
+
const messages = results.flatMap((result) => result.messages);
|
|
1191
|
+
return {
|
|
1192
|
+
errors: messages.filter((message) => message.severity === 2),
|
|
1193
|
+
warnings: messages.filter((message) => message.severity === 1)
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
async#checkFile(filePath, { write = false } = {}) {
|
|
1197
|
+
const originalContent = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
|
|
1198
|
+
const { stdout, stderr } = await this.#runBiome([
|
|
1199
|
+
"check",
|
|
1200
|
+
...write ? ["--write"] : [],
|
|
1201
|
+
"--reporter=json",
|
|
1202
|
+
"--max-diagnostics=none",
|
|
1203
|
+
"--no-errors-on-unmatched",
|
|
1204
|
+
"--config-path",
|
|
1205
|
+
path3.join(this.lintRoot, "biome.json"),
|
|
1206
|
+
this.#toBiomePath(filePath)
|
|
1207
|
+
]);
|
|
1208
|
+
const report = this.#parseBiomeReport(stdout || stderr);
|
|
1209
|
+
const results = this.#toLintResults(report, filePath);
|
|
1210
|
+
const { errors, warnings } = this.#splitMessages(results);
|
|
1211
|
+
const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
|
|
1212
|
+
return {
|
|
1213
|
+
fixed: write && output !== originalContent,
|
|
1214
|
+
output,
|
|
1215
|
+
results,
|
|
1216
|
+
errors,
|
|
1217
|
+
warnings
|
|
1218
|
+
};
|
|
1007
1219
|
}
|
|
1008
1220
|
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
1009
1221
|
if (fix)
|
|
@@ -1011,9 +1223,10 @@ class Linter {
|
|
|
1011
1223
|
return await this.lintFile(filePath);
|
|
1012
1224
|
}
|
|
1013
1225
|
async lintFile(filePath) {
|
|
1014
|
-
|
|
1226
|
+
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
1227
|
+
if (!existsSync(resolvedFilePath))
|
|
1015
1228
|
throw new Error(`File not found: ${filePath}`);
|
|
1016
|
-
return
|
|
1229
|
+
return await this.#checkFile(resolvedFilePath);
|
|
1017
1230
|
}
|
|
1018
1231
|
formatLintResults(results) {
|
|
1019
1232
|
if (results.length === 0)
|
|
@@ -1033,12 +1246,12 @@ ${chalk2.cyan(result.filePath)}`);
|
|
|
1033
1246
|
const sourceContent = readFileSync(result.filePath, "utf8");
|
|
1034
1247
|
sourceLines = sourceContent.split(`
|
|
1035
1248
|
`);
|
|
1036
|
-
} catch
|
|
1249
|
+
} catch {}
|
|
1037
1250
|
}
|
|
1038
1251
|
result.messages.forEach((message) => {
|
|
1039
1252
|
const type = message.severity === 2 ? "error" : "warning";
|
|
1040
1253
|
const typeColor = message.severity === 2 ? chalk2.red : chalk2.yellow;
|
|
1041
|
-
const icon = message.severity === 2 ? "
|
|
1254
|
+
const icon = message.severity === 2 ? "x" : "!";
|
|
1042
1255
|
const ruleInfo = message.ruleId ? chalk2.dim(` (${message.ruleId})`) : "";
|
|
1043
1256
|
output.push(`
|
|
1044
1257
|
${icon} ${typeColor(type)}: ${message.message}${ruleInfo}`);
|
|
@@ -1057,7 +1270,7 @@ ${chalk2.dim(`${lineNumber} |`)} ${sourceLine}`);
|
|
|
1057
1270
|
}
|
|
1058
1271
|
});
|
|
1059
1272
|
if (totalErrors === 0 && totalWarnings === 0)
|
|
1060
|
-
return chalk2.bold("
|
|
1273
|
+
return chalk2.bold("No Biome errors or warnings found");
|
|
1061
1274
|
const errorText = totalErrors > 0 ? chalk2.red(`${totalErrors} error(s)`) : "0 errors";
|
|
1062
1275
|
const warningText = totalWarnings > 0 ? chalk2.yellow(`${totalWarnings} warning(s)`) : "0 warnings";
|
|
1063
1276
|
const summary = [`
|
|
@@ -1072,23 +1285,26 @@ ${errorText}, ${warningText} found`];
|
|
|
1072
1285
|
column: message.column,
|
|
1073
1286
|
message: message.message,
|
|
1074
1287
|
ruleId: message.ruleId,
|
|
1075
|
-
severity: message.severity === 2 ? "error" : "warning"
|
|
1076
|
-
fix: message.fix,
|
|
1077
|
-
suggestions: message.suggestions
|
|
1288
|
+
severity: message.severity === 2 ? "error" : "warning"
|
|
1078
1289
|
})));
|
|
1079
1290
|
const stats = results.reduce((acc, result) => ({
|
|
1080
1291
|
errorCount: acc.errorCount + result.errorCount,
|
|
1081
1292
|
warningCount: acc.warningCount + result.warningCount,
|
|
1082
1293
|
fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
|
|
1083
1294
|
fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount
|
|
1084
|
-
}), {
|
|
1295
|
+
}), {
|
|
1296
|
+
errorCount: 0,
|
|
1297
|
+
warningCount: 0,
|
|
1298
|
+
fixableErrorCount: 0,
|
|
1299
|
+
fixableWarningCount: 0
|
|
1300
|
+
});
|
|
1085
1301
|
return { results, details, stats };
|
|
1086
1302
|
}
|
|
1087
1303
|
async hasNoLintErrors(filePath) {
|
|
1088
1304
|
try {
|
|
1089
1305
|
const { results } = await this.lintFile(filePath);
|
|
1090
1306
|
return results.every((result) => result.errorCount === 0);
|
|
1091
|
-
} catch
|
|
1307
|
+
} catch {
|
|
1092
1308
|
return false;
|
|
1093
1309
|
}
|
|
1094
1310
|
}
|
|
@@ -1101,12 +1317,28 @@ ${errorText}, ${warningText} found`];
|
|
|
1101
1317
|
return results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
|
|
1102
1318
|
}
|
|
1103
1319
|
async fixFile(filePath, dryRun = false) {
|
|
1104
|
-
|
|
1320
|
+
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
1321
|
+
if (!existsSync(resolvedFilePath))
|
|
1105
1322
|
throw new Error(`File not found: ${filePath}`);
|
|
1106
|
-
|
|
1323
|
+
if (!dryRun)
|
|
1324
|
+
return await this.#checkFile(resolvedFilePath, { write: true });
|
|
1325
|
+
const source = readFileSync(resolvedFilePath, "utf8");
|
|
1326
|
+
const { stdout } = await this.#runBiome([
|
|
1327
|
+
"check",
|
|
1328
|
+
"--write",
|
|
1329
|
+
"--config-path",
|
|
1330
|
+
path3.join(this.lintRoot, "biome.json"),
|
|
1331
|
+
"--stdin-file-path",
|
|
1332
|
+
this.#toBiomePath(resolvedFilePath)
|
|
1333
|
+
], source);
|
|
1334
|
+
const lintResult = await this.lintFile(resolvedFilePath);
|
|
1335
|
+
return { ...lintResult, fixed: stdout !== source, output: stdout };
|
|
1107
1336
|
}
|
|
1108
1337
|
async getConfigForFile(filePath) {
|
|
1109
|
-
|
|
1338
|
+
const resolvedFilePath = this.#resolveFilePath(filePath);
|
|
1339
|
+
if (!existsSync(resolvedFilePath))
|
|
1340
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1341
|
+
return JSON.parse(readFileSync(path3.join(this.lintRoot, "biome.json"), "utf8"));
|
|
1110
1342
|
}
|
|
1111
1343
|
async getProblematicRules(filePath) {
|
|
1112
1344
|
const { results } = await this.lintFile(filePath);
|
|
@@ -1496,23 +1728,23 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
1496
1728
|
files.filter((filename) => !appRootAllowedFiles.has(filename)).forEach((filename) => {
|
|
1497
1729
|
addViolation(filename, "unsupported app root file");
|
|
1498
1730
|
});
|
|
1499
|
-
dirs.filter((
|
|
1500
|
-
addViolation(
|
|
1731
|
+
dirs.filter((dirname) => !appRootAllowedDirs.has(dirname)).forEach((dirname) => {
|
|
1732
|
+
addViolation(dirname, "unsupported app root folder");
|
|
1501
1733
|
});
|
|
1502
1734
|
}
|
|
1503
1735
|
libRoot.files.filter((filename) => !isAllowedLibRootFile(filename)).forEach((filename) => {
|
|
1504
1736
|
addViolation(path5.join("lib", filename), "unsupported lib root file");
|
|
1505
1737
|
});
|
|
1506
|
-
libRoot.dirs.filter((
|
|
1507
|
-
addViolation(path5.join("lib",
|
|
1738
|
+
libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
|
|
1739
|
+
addViolation(path5.join("lib", dirname), "unsupported internal lib folder");
|
|
1508
1740
|
});
|
|
1509
|
-
const databaseDirs = libRoot.dirs.filter((
|
|
1510
|
-
const serviceDirs = libRoot.dirs.filter((
|
|
1741
|
+
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
1742
|
+
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
1511
1743
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
1512
1744
|
await Promise.all([
|
|
1513
|
-
...databaseDirs.map((
|
|
1514
|
-
...serviceDirs.map((
|
|
1515
|
-
...scalarDirs.map((
|
|
1745
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path5.join("lib", dirname))),
|
|
1746
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path5.join("lib", dirname))),
|
|
1747
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path5.join("lib/__scalar", dirname)))
|
|
1516
1748
|
]);
|
|
1517
1749
|
if (violations.length > 0) {
|
|
1518
1750
|
throw new Error(`[scan-convention]
|
|
@@ -1522,8 +1754,8 @@ ${violations.sort().map((violation) => `- ${violation}`).join(`
|
|
|
1522
1754
|
}
|
|
1523
1755
|
async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
1524
1756
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
1525
|
-
dirs.forEach((
|
|
1526
|
-
violations.push(`${getScanPath(exec, path5.join(modulePath,
|
|
1757
|
+
dirs.forEach((dirname) => {
|
|
1758
|
+
violations.push(`${getScanPath(exec, path5.join(modulePath, dirname))}: unsupported module folder`);
|
|
1527
1759
|
});
|
|
1528
1760
|
files.forEach((filename) => {
|
|
1529
1761
|
const filePath = path5.join(modulePath, filename);
|
|
@@ -1622,9 +1854,9 @@ class ScanInfo {
|
|
|
1622
1854
|
files.zone.databases.push(name);
|
|
1623
1855
|
});
|
|
1624
1856
|
}),
|
|
1625
|
-
...serviceDirs.map(async (
|
|
1626
|
-
const name =
|
|
1627
|
-
const filenames = await exec.readdir(path5.join("lib",
|
|
1857
|
+
...serviceDirs.map(async (dirname) => {
|
|
1858
|
+
const name = dirname.slice(1);
|
|
1859
|
+
const filenames = await exec.readdir(path5.join("lib", dirname));
|
|
1628
1860
|
filenames.forEach((filename) => {
|
|
1629
1861
|
if (filename.endsWith(".dictionary.ts"))
|
|
1630
1862
|
files.dictionary.services.push(name);
|
|
@@ -2254,9 +2486,11 @@ var ROOT_LAYOUT_EXPORTS = new Set([
|
|
|
2254
2486
|
"reconnect",
|
|
2255
2487
|
"layoutStyle",
|
|
2256
2488
|
"gaTrackingId",
|
|
2257
|
-
"Loading"
|
|
2489
|
+
"Loading",
|
|
2490
|
+
"NotFound",
|
|
2491
|
+
"Error"
|
|
2258
2492
|
]);
|
|
2259
|
-
var LAYOUT_ROUTE_EXPORTS = new Set(["default", "head", "generateHead", "Loading"]);
|
|
2493
|
+
var LAYOUT_ROUTE_EXPORTS = new Set(["default", "head", "generateHead", "Loading", "NotFound", "Error"]);
|
|
2260
2494
|
function validateRouteSourceExports(source, filePath, kind, options = {}) {
|
|
2261
2495
|
const sourceFile = ts3.createSourceFile(filePath, source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
2262
2496
|
const allowed = kind === "page" ? PAGE_ROUTE_EXPORTS : options.rootLayout ? ROOT_LAYOUT_EXPORTS : LAYOUT_ROUTE_EXPORTS;
|
|
@@ -2325,16 +2559,16 @@ class Executor {
|
|
|
2325
2559
|
linter = null;
|
|
2326
2560
|
constructor(name, cwdPath) {
|
|
2327
2561
|
this.name = name;
|
|
2328
|
-
this.logger = new
|
|
2562
|
+
this.logger = new Logger3(name);
|
|
2329
2563
|
this.logs = [];
|
|
2330
2564
|
this.cwdPath = cwdPath;
|
|
2331
2565
|
}
|
|
2332
2566
|
#stdout(data) {
|
|
2333
2567
|
if (Executor.verbose)
|
|
2334
|
-
|
|
2568
|
+
Logger3.raw(chalk4.dim(data.toString()));
|
|
2335
2569
|
}
|
|
2336
2570
|
#stderr(data) {
|
|
2337
|
-
|
|
2571
|
+
Logger3.raw(chalk4.red(data.toString()));
|
|
2338
2572
|
}
|
|
2339
2573
|
exec(command, options = {}) {
|
|
2340
2574
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
@@ -2378,7 +2612,7 @@ class Executor {
|
|
|
2378
2612
|
}
|
|
2379
2613
|
spawn(command, args = [], options = {}) {
|
|
2380
2614
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
2381
|
-
const proc =
|
|
2615
|
+
const proc = spawn2(command, args, {
|
|
2382
2616
|
cwd: this.cwdPath,
|
|
2383
2617
|
...options
|
|
2384
2618
|
});
|
|
@@ -2424,7 +2658,7 @@ class Executor {
|
|
|
2424
2658
|
});
|
|
2425
2659
|
}
|
|
2426
2660
|
spawnSync(command, args = [], options = {}) {
|
|
2427
|
-
const proc =
|
|
2661
|
+
const proc = spawn2(command, args, {
|
|
2428
2662
|
cwd: this.cwdPath,
|
|
2429
2663
|
...options
|
|
2430
2664
|
});
|
|
@@ -2556,7 +2790,7 @@ class Executor {
|
|
|
2556
2790
|
contentStr = currentContent;
|
|
2557
2791
|
} else {
|
|
2558
2792
|
await FileSys.writeText(writePath, contentStr);
|
|
2559
|
-
if (
|
|
2793
|
+
if (Logger3.isVerbose())
|
|
2560
2794
|
this.logger.rawLog(chalk4.yellow(`File Update: ${filePath}`));
|
|
2561
2795
|
}
|
|
2562
2796
|
} else {
|
|
@@ -2665,8 +2899,8 @@ class Executor {
|
|
|
2665
2899
|
return null;
|
|
2666
2900
|
const filename = typeof result === "object" ? result.filename : path7.basename(targetPath).replace(".js", ".ts");
|
|
2667
2901
|
const content = typeof result === "object" ? result.content : result;
|
|
2668
|
-
const
|
|
2669
|
-
const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), `${
|
|
2902
|
+
const dirname2 = path7.dirname(targetPath);
|
|
2903
|
+
const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), `${dirname2}/${filename}`);
|
|
2670
2904
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2671
2905
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
2672
2906
|
} else if (targetPath.endsWith(".template")) {
|
|
@@ -2680,9 +2914,9 @@ class Executor {
|
|
|
2680
2914
|
} else if (staticTemplateFileExtensions.has(path7.extname(targetPath).toLowerCase())) {
|
|
2681
2915
|
const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
|
|
2682
2916
|
const writePath = this.getPath(convertedTargetPath);
|
|
2683
|
-
const
|
|
2684
|
-
if (!await FileSys.dirExists(
|
|
2685
|
-
await mkdir2(
|
|
2917
|
+
const dirname2 = path7.dirname(writePath);
|
|
2918
|
+
if (!await FileSys.dirExists(dirname2))
|
|
2919
|
+
await mkdir2(dirname2, { recursive: true });
|
|
2686
2920
|
await copyFile(templatePath, writePath);
|
|
2687
2921
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2688
2922
|
return { filePath: writePath, content: "" };
|
|
@@ -2763,6 +2997,48 @@ class Executor {
|
|
|
2763
2997
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
2764
2998
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
2765
2999
|
}
|
|
3000
|
+
async typeCheckAsync(filePath) {
|
|
3001
|
+
const path8 = this.getPath(filePath);
|
|
3002
|
+
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
3003
|
+
const proc = Bun.spawn([process.execPath, entry], {
|
|
3004
|
+
cwd: this.cwdPath,
|
|
3005
|
+
env: {
|
|
3006
|
+
...process.env,
|
|
3007
|
+
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
3008
|
+
AKAN_TYPECHECK_FILE: path8
|
|
3009
|
+
},
|
|
3010
|
+
stdout: "pipe",
|
|
3011
|
+
stderr: "pipe"
|
|
3012
|
+
});
|
|
3013
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
3014
|
+
new Response(proc.stdout).text(),
|
|
3015
|
+
new Response(proc.stderr).text(),
|
|
3016
|
+
proc.exited
|
|
3017
|
+
]);
|
|
3018
|
+
if (exitCode !== 0)
|
|
3019
|
+
throw new Error((stderr || stdout).trim() || `Typecheck failed with exit code ${exitCode}`);
|
|
3020
|
+
const result = JSON.parse(stdout);
|
|
3021
|
+
return {
|
|
3022
|
+
fileDiagnostics: Array.from({ length: result.fileDiagnosticsCount }),
|
|
3023
|
+
fileErrors: Array.from({ length: result.fileErrorsCount }),
|
|
3024
|
+
fileWarnings: Array.from({ length: result.fileWarningsCount }),
|
|
3025
|
+
message: result.message
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
async#resolveTypecheckWorkerEntry() {
|
|
3029
|
+
const dirname2 = getDirname(import.meta.url);
|
|
3030
|
+
const candidates = [
|
|
3031
|
+
path7.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
3032
|
+
path7.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
3033
|
+
path7.join(dirname2, "typecheck/typecheck.proc.ts"),
|
|
3034
|
+
path7.join(dirname2, "typecheck.proc.js"),
|
|
3035
|
+
path7.join(dirname2, "typecheck.proc.ts")
|
|
3036
|
+
];
|
|
3037
|
+
for (const candidate of candidates)
|
|
3038
|
+
if (await Bun.file(candidate).exists())
|
|
3039
|
+
return candidate;
|
|
3040
|
+
throw new Error(`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`);
|
|
3041
|
+
}
|
|
2766
3042
|
getLinter() {
|
|
2767
3043
|
this.linter ??= new Linter(this.cwdPath);
|
|
2768
3044
|
return this.linter;
|
|
@@ -2810,7 +3086,15 @@ class WorkspaceExecutor extends Executor {
|
|
|
2810
3086
|
const env = sourceEnv.AKAN_PUBLIC_ENV ?? "debug";
|
|
2811
3087
|
if (!env)
|
|
2812
3088
|
throw new Error("AKAN_PUBLIC_ENV is not set");
|
|
2813
|
-
return {
|
|
3089
|
+
return {
|
|
3090
|
+
...appName ? { appName } : {},
|
|
3091
|
+
workspaceRoot,
|
|
3092
|
+
repoName,
|
|
3093
|
+
serveDomain,
|
|
3094
|
+
env,
|
|
3095
|
+
portOffset,
|
|
3096
|
+
workspaceId
|
|
3097
|
+
};
|
|
2814
3098
|
}
|
|
2815
3099
|
getWorkspaceId({
|
|
2816
3100
|
allowEmpty
|
|
@@ -2870,12 +3154,12 @@ class WorkspaceExecutor extends Executor {
|
|
|
2870
3154
|
}
|
|
2871
3155
|
async getDirInModule(basePath2, name) {
|
|
2872
3156
|
const AVOID_DIRS = ["__lib", "__scalar", `_`, `_${name}`];
|
|
2873
|
-
const getDirs = async (
|
|
2874
|
-
const dirs = await this.readdir(
|
|
3157
|
+
const getDirs = async (dirname2, maxDepth = 3, results = [], prefix = "") => {
|
|
3158
|
+
const dirs = await this.readdir(dirname2);
|
|
2875
3159
|
await Promise.all(dirs.map(async (dir) => {
|
|
2876
3160
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2877
3161
|
return;
|
|
2878
|
-
const dirPath = path7.join(
|
|
3162
|
+
const dirPath = path7.join(dirname2, dir);
|
|
2879
3163
|
if ((await stat2(dirPath)).isDirectory()) {
|
|
2880
3164
|
results.push(`${prefix}${dir}`);
|
|
2881
3165
|
if (maxDepth > 0)
|
|
@@ -2895,12 +3179,12 @@ class WorkspaceExecutor extends Executor {
|
|
|
2895
3179
|
}
|
|
2896
3180
|
async#getDirHasFile(basePath2, targetFilename) {
|
|
2897
3181
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
2898
|
-
const getDirs = async (
|
|
2899
|
-
const dirs = await this.readdir(
|
|
3182
|
+
const getDirs = async (dirname2, maxDepth = 3, results = [], prefix = "") => {
|
|
3183
|
+
const dirs = await this.readdir(dirname2);
|
|
2900
3184
|
await Promise.all(dirs.map(async (dir) => {
|
|
2901
3185
|
if (AVOID_DIRS.includes(dir))
|
|
2902
3186
|
return;
|
|
2903
|
-
const dirPath = path7.join(
|
|
3187
|
+
const dirPath = path7.join(dirname2, dir);
|
|
2904
3188
|
if ((await stat2(dirPath)).isDirectory()) {
|
|
2905
3189
|
const hasTargetFile = await FileSys.fileExists(path7.join(dirPath, targetFilename));
|
|
2906
3190
|
if (hasTargetFile)
|
|
@@ -3165,6 +3449,13 @@ class AppExecutor extends SysExecutor {
|
|
|
3165
3449
|
getEnv() {
|
|
3166
3450
|
return WorkspaceExecutor.getBaseDevEnv().env;
|
|
3167
3451
|
}
|
|
3452
|
+
async getDevPort() {
|
|
3453
|
+
const basePort = 8282;
|
|
3454
|
+
const appNames = (await this.workspace.getApps()).sort((a, b) => a.localeCompare(b));
|
|
3455
|
+
const appIndex = Math.max(appNames.indexOf(this.name), 0);
|
|
3456
|
+
const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
|
|
3457
|
+
return basePort + appIndex + portOffset;
|
|
3458
|
+
}
|
|
3168
3459
|
getCommandEnv(env = {}) {
|
|
3169
3460
|
const basePort = 8282;
|
|
3170
3461
|
const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
|
|
@@ -3198,7 +3489,12 @@ class AppExecutor extends SysExecutor {
|
|
|
3198
3489
|
]);
|
|
3199
3490
|
} else
|
|
3200
3491
|
await this.removeDir(".akan");
|
|
3201
|
-
const
|
|
3492
|
+
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
3493
|
+
const env = this.getCommandEnv({
|
|
3494
|
+
AKAN_COMMAND_TYPE: type,
|
|
3495
|
+
...routeEnv,
|
|
3496
|
+
...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
|
|
3497
|
+
});
|
|
3202
3498
|
return { env };
|
|
3203
3499
|
}
|
|
3204
3500
|
#publicEnv = null;
|
|
@@ -3617,7 +3913,7 @@ var createTunnel = async (service, { app, environment, port = service === "postg
|
|
|
3617
3913
|
|
|
3618
3914
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
|
|
3619
3915
|
import path8 from "path";
|
|
3620
|
-
import { Logger as
|
|
3916
|
+
import { Logger as Logger4 } from "akanjs/common";
|
|
3621
3917
|
var builderMsgTypeSet = new Set([
|
|
3622
3918
|
"build-route-res",
|
|
3623
3919
|
"builder-ready",
|
|
@@ -3629,7 +3925,7 @@ var builderMsgTypeSet = new Set([
|
|
|
3629
3925
|
class IncrementalBuilderHost {
|
|
3630
3926
|
static #restartBaseDelayMs = 1000;
|
|
3631
3927
|
static #restartMaxDelayMs = 30000;
|
|
3632
|
-
logger = new
|
|
3928
|
+
logger = new Logger4("IncrementalBuilderHost");
|
|
3633
3929
|
entry;
|
|
3634
3930
|
env;
|
|
3635
3931
|
app;
|
|
@@ -3877,7 +4173,7 @@ class BackendImportGraph {
|
|
|
3877
4173
|
|
|
3878
4174
|
class AkanAppHost {
|
|
3879
4175
|
app;
|
|
3880
|
-
logger = new
|
|
4176
|
+
logger = new Logger5("AkanAppHost");
|
|
3881
4177
|
withInk;
|
|
3882
4178
|
env;
|
|
3883
4179
|
#backend = null;
|
|
@@ -4160,19 +4456,19 @@ class AkanAppHost {
|
|
|
4160
4456
|
}
|
|
4161
4457
|
}
|
|
4162
4458
|
// pkgs/@akanjs/devkit/applicationBuildReporter.ts
|
|
4163
|
-
import { Logger as
|
|
4459
|
+
import { Logger as Logger6 } from "akanjs/common";
|
|
4164
4460
|
|
|
4165
4461
|
class ApplicationBuildReporter {
|
|
4166
4462
|
static create() {
|
|
4167
4463
|
return {
|
|
4168
|
-
phaseDone: (phase) =>
|
|
4464
|
+
phaseDone: (phase) => Logger6.rawLog(ApplicationBuildReporter.formatPhaseLine(phase))
|
|
4169
4465
|
};
|
|
4170
4466
|
}
|
|
4171
4467
|
static printSummary(result) {
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4468
|
+
Logger6.rawLog("");
|
|
4469
|
+
Logger6.rawLog(`Route artifacts: ${result.artifactDir}`);
|
|
4470
|
+
Logger6.rawLog(`Server output: ${result.outputDir}`);
|
|
4471
|
+
Logger6.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
|
|
4176
4472
|
}
|
|
4177
4473
|
static formatError(error) {
|
|
4178
4474
|
if (error instanceof AggregateError) {
|
|
@@ -4324,6 +4620,9 @@ export async function generateHead(props: PageProps) {
|
|
|
4324
4620
|
return inheritedLayout.head;
|
|
4325
4621
|
}
|
|
4326
4622
|
|
|
4623
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
4624
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
4625
|
+
|
|
4327
4626
|
export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
|
|
4328
4627
|
return (
|
|
4329
4628
|
<System.Provider
|
|
@@ -4353,6 +4652,9 @@ export async function generateHead(props: PageProps) {
|
|
|
4353
4652
|
return inheritedLayout.head;
|
|
4354
4653
|
}
|
|
4355
4654
|
|
|
4655
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
4656
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
4657
|
+
|
|
4356
4658
|
export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
|
|
4357
4659
|
return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
|
|
4358
4660
|
}
|
|
@@ -4471,13 +4773,13 @@ import path14 from "path";
|
|
|
4471
4773
|
|
|
4472
4774
|
// pkgs/@akanjs/devkit/transforms/barrelAnalyzer.ts
|
|
4473
4775
|
import path12 from "path";
|
|
4474
|
-
import { Logger as
|
|
4776
|
+
import { Logger as Logger7 } from "akanjs/common";
|
|
4475
4777
|
var REEXPORT_RE = /(?:^|\n)\s*export\s+(?:type\s+)?(?:(\*)(?:\s+as\s+(\w+))?|\{\s*([^}]*?)\s*\})\s+from\s+(["'])([^"']+)\4;?/g;
|
|
4476
4778
|
var LOCAL_NAMED_RE = /(?:^|\n)\s*export\s+\{\s*([^}]*?)\s*\}(?!\s*from)/g;
|
|
4477
4779
|
var CANDIDATE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4478
4780
|
|
|
4479
4781
|
class BarrelAnalyzer {
|
|
4480
|
-
#logger = new
|
|
4782
|
+
#logger = new Logger7("BarrelAnalyzer");
|
|
4481
4783
|
#opts;
|
|
4482
4784
|
#cache = new Map;
|
|
4483
4785
|
#tsTranspiler = new Bun.Transpiler({ loader: "ts" });
|
|
@@ -6177,7 +6479,7 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
|
6177
6479
|
}
|
|
6178
6480
|
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
6179
6481
|
import path23 from "path";
|
|
6180
|
-
import { Logger as
|
|
6482
|
+
import { Logger as Logger8 } from "akanjs/common";
|
|
6181
6483
|
import { compile } from "tailwindcss";
|
|
6182
6484
|
|
|
6183
6485
|
// pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
|
|
@@ -6322,7 +6624,7 @@ var NODE_MODULES_RE3 = /[\\/]node_modules[\\/]/;
|
|
|
6322
6624
|
var AKANJS_NODE_MODULE_RE3 = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
6323
6625
|
|
|
6324
6626
|
class CssCompiler {
|
|
6325
|
-
#logger = new
|
|
6627
|
+
#logger = new Logger8("CssCompiler");
|
|
6326
6628
|
#transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
6327
6629
|
#app;
|
|
6328
6630
|
#cssImportResolver = null;
|
|
@@ -7730,7 +8032,7 @@ class ApplicationBuildRunner {
|
|
|
7730
8032
|
import { cp, mkdir as mkdir8, rm as rm3 } from "fs/promises";
|
|
7731
8033
|
|
|
7732
8034
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
7733
|
-
import { HttpClient as HttpClient2, Logger as
|
|
8035
|
+
import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
|
|
7734
8036
|
var spinning = (message) => {
|
|
7735
8037
|
const spinner = new Spinner(message, { prefix: message, enableSpin: true }).start();
|
|
7736
8038
|
return spinner;
|
|
@@ -7743,7 +8045,7 @@ var uploadRelease = async (appName, {
|
|
|
7743
8045
|
os,
|
|
7744
8046
|
local
|
|
7745
8047
|
}) => {
|
|
7746
|
-
const logger = new
|
|
8048
|
+
const logger = new Logger9("uploadRelease");
|
|
7747
8049
|
const basePath2 = local ? "http://localhost:8282/backend" : "https://cloud.akanjs.com/backend";
|
|
7748
8050
|
const httpClient = new HttpClient2(basePath2);
|
|
7749
8051
|
const buildPath = `${workspaceRoot}/releases/builds/${appName}-release.tar.gz`;
|
|
@@ -8729,7 +9031,7 @@ var Workspace = createInternalArgToken("Workspace");
|
|
|
8729
9031
|
// pkgs/@akanjs/devkit/commandDecorators/command.ts
|
|
8730
9032
|
import path36 from "path";
|
|
8731
9033
|
import { confirm, input as input2, select as select2 } from "@inquirer/prompts";
|
|
8732
|
-
import { Logger as
|
|
9034
|
+
import { Logger as Logger10 } from "akanjs/common";
|
|
8733
9035
|
import chalk6 from "chalk";
|
|
8734
9036
|
import { program } from "commander";
|
|
8735
9037
|
|
|
@@ -9014,7 +9316,7 @@ var printCliError = (error) => {
|
|
|
9014
9316
|
if (loggedCliErrorMessages.has(message))
|
|
9015
9317
|
return;
|
|
9016
9318
|
loggedCliErrorMessages.add(message);
|
|
9017
|
-
|
|
9319
|
+
Logger10.rawLog(`
|
|
9018
9320
|
${chalk6.red(message)}`);
|
|
9019
9321
|
};
|
|
9020
9322
|
var handleOption = (programCommand, argMeta) => {
|
|
@@ -9243,7 +9545,7 @@ var runCommands = async (...commands) => {
|
|
|
9243
9545
|
const hasCommand = process.argv.length > 2 && !process.argv[2]?.startsWith("-");
|
|
9244
9546
|
if (hasHelpFlag || !hasCommand) {
|
|
9245
9547
|
if (process.argv.length === 2 || process.argv.length === 3 && hasHelpFlag) {
|
|
9246
|
-
|
|
9548
|
+
Logger10.rawLog(formatHelp(commands, process.env.AKAN_VERSION));
|
|
9247
9549
|
process.exit(0);
|
|
9248
9550
|
}
|
|
9249
9551
|
}
|
|
@@ -9252,7 +9554,7 @@ var runCommands = async (...commands) => {
|
|
|
9252
9554
|
});
|
|
9253
9555
|
const installedAkanPackageJson = await FileSys.fileExists("./node_modules/akanjs/package.json") ? await FileSys.readJson("./node_modules/akanjs/package.json") : null;
|
|
9254
9556
|
if (installedAkanPackageJson && installedAkanPackageJson.version !== process.env.AKAN_VERSION) {
|
|
9255
|
-
|
|
9557
|
+
Logger10.rawLog(chalk6.yellow(`
|
|
9256
9558
|
Akan CLI version is mismatch with installed package. ${process.env.AKAN_VERSION} (global) vs ${installedAkanPackageJson.version} (akanjs)
|
|
9257
9559
|
It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`));
|
|
9258
9560
|
}
|
|
@@ -9288,7 +9590,7 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
|
|
|
9288
9590
|
return formatCommandHelp(command, targetMeta.key);
|
|
9289
9591
|
};
|
|
9290
9592
|
programCommand.action(async (...args) => {
|
|
9291
|
-
|
|
9593
|
+
Logger10.rawLog();
|
|
9292
9594
|
const cmdArgs = args.slice(0, args.length - 2);
|
|
9293
9595
|
const opt = args[args.length - 2];
|
|
9294
9596
|
const commandArgs = [];
|
|
@@ -9312,7 +9614,7 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
|
|
|
9312
9614
|
const cmd = CommandContainer.get(command);
|
|
9313
9615
|
try {
|
|
9314
9616
|
await targetMeta.handler.call(cmd, ...commandArgs);
|
|
9315
|
-
|
|
9617
|
+
Logger10.rawLog();
|
|
9316
9618
|
} catch (e) {
|
|
9317
9619
|
printCliError(e);
|
|
9318
9620
|
throw e;
|
|
@@ -9652,8 +9954,11 @@ import fsPromise from "fs/promises";
|
|
|
9652
9954
|
import { input as input3, select as select3 } from "@inquirer/prompts";
|
|
9653
9955
|
class Prompter {
|
|
9654
9956
|
static async#getGuidelineRoot() {
|
|
9655
|
-
const
|
|
9656
|
-
const candidates = [
|
|
9957
|
+
const dirname2 = getDirname(import.meta.url);
|
|
9958
|
+
const candidates = [
|
|
9959
|
+
`${dirname2}/guidelines`,
|
|
9960
|
+
`${dirname2}/../cli/guidelines`
|
|
9961
|
+
];
|
|
9657
9962
|
for (const candidate of candidates) {
|
|
9658
9963
|
try {
|
|
9659
9964
|
await fsPromise.access(candidate);
|
|
@@ -9665,7 +9970,10 @@ class Prompter {
|
|
|
9665
9970
|
static async selectGuideline() {
|
|
9666
9971
|
const guidelineRoot = await Prompter.#getGuidelineRoot();
|
|
9667
9972
|
const guideNames = (await fsPromise.readdir(guidelineRoot)).filter((name) => !name.startsWith("_"));
|
|
9668
|
-
return await select3({
|
|
9973
|
+
return await select3({
|
|
9974
|
+
message: "Select a guideline",
|
|
9975
|
+
choices: guideNames.map((name) => ({ name, value: name }))
|
|
9976
|
+
});
|
|
9669
9977
|
}
|
|
9670
9978
|
static async getGuideJson(guideName) {
|
|
9671
9979
|
const guidelineRoot = await Prompter.#getGuidelineRoot();
|
|
@@ -9680,13 +9988,18 @@ class Prompter {
|
|
|
9680
9988
|
return content;
|
|
9681
9989
|
}
|
|
9682
9990
|
static async getUpdateRequest(guideName) {
|
|
9683
|
-
return await input3({
|
|
9991
|
+
return await input3({
|
|
9992
|
+
message: `What do you want to update in ${guideName}?`
|
|
9993
|
+
});
|
|
9684
9994
|
}
|
|
9685
9995
|
async makeTsFileUpdatePrompt({ context, request }) {
|
|
9686
9996
|
return `You are a senior developer writing TypeScript-based programs using Akan.js, an in-house framework. Here's an overview of the Akan.js framework:
|
|
9687
9997
|
${await this.getDocumentation("framework")}
|
|
9688
9998
|
Please understand the following background information, write code that meets the requirements, verify that it satisfies the validation conditions, and return the result.
|
|
9689
9999
|
|
|
10000
|
+
# Code Style
|
|
10001
|
+
- Use double quotes for all string literals in TypeScript/TSX code. Do not use single quotes.
|
|
10002
|
+
|
|
9690
10003
|
# Background Information
|
|
9691
10004
|
\`\`\`markdown
|
|
9692
10005
|
${context}
|
|
@@ -9733,10 +10046,10 @@ import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
|
9733
10046
|
import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
9734
10047
|
"use client";
|
|
9735
10048
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
9736
|
-
import { Logger as
|
|
10049
|
+
import { Logger as Logger11 } from "akanjs/common";
|
|
9737
10050
|
|
|
9738
10051
|
class IncrementalBuilder {
|
|
9739
|
-
#logger = new
|
|
10052
|
+
#logger = new Logger11("IncrementalBuilder");
|
|
9740
10053
|
#app;
|
|
9741
10054
|
#artifact;
|
|
9742
10055
|
#watch;
|