@akanjs/cli 2.3.9-rc.9 → 2.3.9
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 +637 -79
- package/index.js +721 -124
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
4
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
5
|
-
import
|
|
5
|
+
import path44 from "path";
|
|
6
6
|
|
|
7
7
|
// pkgs/@akanjs/devkit/aiEditor.ts
|
|
8
8
|
import { input, select } from "@inquirer/prompts";
|
|
@@ -12504,6 +12504,7 @@ void run().catch((error) => {
|
|
|
12504
12504
|
}
|
|
12505
12505
|
// pkgs/@akanjs/devkit/applicationReleasePackager.ts
|
|
12506
12506
|
import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
|
|
12507
|
+
import path38 from "path";
|
|
12507
12508
|
|
|
12508
12509
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
12509
12510
|
import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
|
|
@@ -12619,13 +12620,9 @@ class ApplicationReleasePackager {
|
|
|
12619
12620
|
await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
|
|
12620
12621
|
await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
|
|
12621
12622
|
await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
"-C",
|
|
12626
|
-
buildRoot,
|
|
12627
|
-
"./"
|
|
12628
|
-
]);
|
|
12623
|
+
const releaseRoot = this.#app.workspace.workspaceRoot;
|
|
12624
|
+
const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
|
|
12625
|
+
await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
|
|
12629
12626
|
await this.#writeCsrZipIfPresent();
|
|
12630
12627
|
return { platformVersion };
|
|
12631
12628
|
}
|
|
@@ -12646,22 +12643,18 @@ class ApplicationReleasePackager {
|
|
|
12646
12643
|
await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
|
|
12647
12644
|
const libDeps = ["social", "shared", "platform", "util"];
|
|
12648
12645
|
await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
|
|
12649
|
-
await Promise.all([".next", "ios", "android", "public/libs"].map(async (
|
|
12650
|
-
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${
|
|
12646
|
+
await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
|
|
12647
|
+
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
|
|
12651
12648
|
if (await FileSys.dirExists(targetPath))
|
|
12652
12649
|
await rm4(targetPath, { recursive: true, force: true });
|
|
12653
12650
|
}));
|
|
12654
12651
|
const syncPaths = [".husky", ".gitignore", "package.json"];
|
|
12655
|
-
await Promise.all(syncPaths.map((
|
|
12652
|
+
await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
|
|
12656
12653
|
await this.#writeSourceTsconfig(sourceRoot, libDeps);
|
|
12657
12654
|
await Bun.write(`${sourceRoot}/README.md`, readme);
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
"-C",
|
|
12662
|
-
sourceRoot,
|
|
12663
|
-
"./"
|
|
12664
|
-
]);
|
|
12655
|
+
const sourceCwd = this.#app.workspace.cwdPath;
|
|
12656
|
+
const sourceArchivePath = path38.relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`).split(path38.sep).join("/");
|
|
12657
|
+
await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
|
|
12665
12658
|
}
|
|
12666
12659
|
async#resetSourceRoot(sourceRoot) {
|
|
12667
12660
|
if (await FileSys.dirExists(sourceRoot)) {
|
|
@@ -12743,20 +12736,20 @@ class ApplicationReleasePackager {
|
|
|
12743
12736
|
}
|
|
12744
12737
|
}
|
|
12745
12738
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
12746
|
-
import
|
|
12739
|
+
import path39 from "path";
|
|
12747
12740
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
12748
12741
|
async function resolveSignalTestPreloadPath(target) {
|
|
12749
12742
|
const candidates = [];
|
|
12750
12743
|
const addResolvedPackageCandidate = (basePath2) => {
|
|
12751
12744
|
try {
|
|
12752
|
-
candidates.push(
|
|
12745
|
+
candidates.push(path39.join(path39.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
|
|
12753
12746
|
} catch {}
|
|
12754
12747
|
};
|
|
12755
12748
|
addResolvedPackageCandidate(target.cwdPath);
|
|
12756
12749
|
addResolvedPackageCandidate(process.cwd());
|
|
12757
|
-
addResolvedPackageCandidate(
|
|
12750
|
+
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12758
12751
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12759
|
-
candidates.push(
|
|
12752
|
+
candidates.push(path39.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(path39.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
12760
12753
|
for (const candidate of [...new Set(candidates)]) {
|
|
12761
12754
|
if (await Bun.file(candidate).exists())
|
|
12762
12755
|
return candidate;
|
|
@@ -12766,7 +12759,7 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12766
12759
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12767
12760
|
import { existsSync as existsSync2 } from "fs";
|
|
12768
12761
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
12769
|
-
import
|
|
12762
|
+
import path40 from "path";
|
|
12770
12763
|
var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
|
|
12771
12764
|
var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
|
|
12772
12765
|
var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
|
|
@@ -12783,14 +12776,14 @@ class Builder {
|
|
|
12783
12776
|
#globEntrypoints(cwd, pattern) {
|
|
12784
12777
|
const glob = new Bun.Glob(pattern);
|
|
12785
12778
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12786
|
-
const segments = relativePath.split(
|
|
12779
|
+
const segments = relativePath.split(path40.sep);
|
|
12787
12780
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12788
|
-
}).map((rel) =>
|
|
12781
|
+
}).map((rel) => path40.join(cwd, rel));
|
|
12789
12782
|
}
|
|
12790
12783
|
#globFiles(cwd, pattern = "**/*.*") {
|
|
12791
12784
|
const glob = new Bun.Glob(pattern);
|
|
12792
12785
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12793
|
-
const segments = relativePath.split(
|
|
12786
|
+
const segments = relativePath.split(path40.sep);
|
|
12794
12787
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12795
12788
|
});
|
|
12796
12789
|
}
|
|
@@ -12798,17 +12791,17 @@ class Builder {
|
|
|
12798
12791
|
const out = [];
|
|
12799
12792
|
for (const p of additionalEntryPoints) {
|
|
12800
12793
|
if (p.includes("*")) {
|
|
12801
|
-
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${
|
|
12794
|
+
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path40.sep}`) ? p.slice(cwd.length + 1) : p;
|
|
12802
12795
|
out.push(...this.#globEntrypoints(cwd, rel));
|
|
12803
12796
|
} else
|
|
12804
|
-
out.push(
|
|
12797
|
+
out.push(path40.isAbsolute(p) ? p : path40.join(cwd, p));
|
|
12805
12798
|
}
|
|
12806
12799
|
return out;
|
|
12807
12800
|
}
|
|
12808
12801
|
#getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
|
|
12809
12802
|
const cwd = this.#executor.cwdPath;
|
|
12810
12803
|
const entrypoints = [
|
|
12811
|
-
...bundle ? [
|
|
12804
|
+
...bundle ? [path40.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
|
|
12812
12805
|
...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
|
|
12813
12806
|
];
|
|
12814
12807
|
return {
|
|
@@ -12829,9 +12822,9 @@ class Builder {
|
|
|
12829
12822
|
for (const relativePath of this.#globFiles(cwd)) {
|
|
12830
12823
|
if (relativePath === "package.json")
|
|
12831
12824
|
continue;
|
|
12832
|
-
const sourcePath =
|
|
12833
|
-
const targetPath =
|
|
12834
|
-
await mkdir10(
|
|
12825
|
+
const sourcePath = path40.join(cwd, relativePath);
|
|
12826
|
+
const targetPath = path40.join(this.#distExecutor.cwdPath, relativePath);
|
|
12827
|
+
await mkdir10(path40.dirname(targetPath), { recursive: true });
|
|
12835
12828
|
await Bun.write(targetPath, Bun.file(sourcePath));
|
|
12836
12829
|
}
|
|
12837
12830
|
}
|
|
@@ -12842,13 +12835,13 @@ class Builder {
|
|
|
12842
12835
|
return withoutFormatDir;
|
|
12843
12836
|
if (!hasDotSlash && withoutFormatDir === publishedPath)
|
|
12844
12837
|
return publishedPath;
|
|
12845
|
-
const parsed =
|
|
12838
|
+
const parsed = path40.posix.parse(withoutFormatDir);
|
|
12846
12839
|
if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
|
|
12847
12840
|
return withoutFormatDir;
|
|
12848
|
-
const withoutExt =
|
|
12841
|
+
const withoutExt = path40.posix.join(parsed.dir, parsed.name);
|
|
12849
12842
|
const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
|
|
12850
12843
|
const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
|
|
12851
|
-
const matchedSource = sourceCandidates.find((candidate) => existsSync2(
|
|
12844
|
+
const matchedSource = sourceCandidates.find((candidate) => existsSync2(path40.join(this.#executor.cwdPath, candidate)));
|
|
12852
12845
|
if (!matchedSource)
|
|
12853
12846
|
return withoutFormatDir;
|
|
12854
12847
|
return hasDotSlash ? `./${matchedSource}` : matchedSource;
|
|
@@ -12899,7 +12892,7 @@ class Builder {
|
|
|
12899
12892
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12900
12893
|
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12901
12894
|
import os from "os";
|
|
12902
|
-
import
|
|
12895
|
+
import path41 from "path";
|
|
12903
12896
|
import { select as select2 } from "@inquirer/prompts";
|
|
12904
12897
|
import { MobileProject } from "@trapezedev/project";
|
|
12905
12898
|
import { capitalize as capitalize5 } from "akanjs/common";
|
|
@@ -13106,13 +13099,13 @@ var rootCapacitorConfigFilenames = [
|
|
|
13106
13099
|
"capacitor.config.js",
|
|
13107
13100
|
"capacitor.config.json"
|
|
13108
13101
|
];
|
|
13109
|
-
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) =>
|
|
13102
|
+
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
|
|
13110
13103
|
async function clearRootCapacitorConfigs(appRoot) {
|
|
13111
13104
|
await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
|
|
13112
13105
|
}
|
|
13113
13106
|
async function writeRootCapacitorConfig(appRoot, content) {
|
|
13114
13107
|
await clearRootCapacitorConfigs(appRoot);
|
|
13115
|
-
await Bun.write(
|
|
13108
|
+
await Bun.write(path41.join(appRoot, "capacitor.config.json"), content);
|
|
13116
13109
|
}
|
|
13117
13110
|
var getLocalIP = () => {
|
|
13118
13111
|
const interfaces = os.networkInterfaces();
|
|
@@ -13221,13 +13214,13 @@ function buildIosNativeRunCommand({
|
|
|
13221
13214
|
scheme = "App",
|
|
13222
13215
|
configuration = "Debug"
|
|
13223
13216
|
}) {
|
|
13224
|
-
const derivedDataPath =
|
|
13217
|
+
const derivedDataPath = path41.join(appRoot, "ios/DerivedData", device.id);
|
|
13225
13218
|
const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
|
|
13226
13219
|
const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
|
|
13227
13220
|
return {
|
|
13228
13221
|
configuration,
|
|
13229
13222
|
derivedDataPath,
|
|
13230
|
-
appPath:
|
|
13223
|
+
appPath: path41.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
|
|
13231
13224
|
xcodebuildArgs: [
|
|
13232
13225
|
"-project",
|
|
13233
13226
|
"App.xcodeproj",
|
|
@@ -13437,7 +13430,7 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13437
13430
|
...capacitorConfig,
|
|
13438
13431
|
appId,
|
|
13439
13432
|
appName,
|
|
13440
|
-
webDir:
|
|
13433
|
+
webDir: path41.posix.join(".akan", "mobile", name, "www"),
|
|
13441
13434
|
plugins: {
|
|
13442
13435
|
CapacitorCookies: { enabled: true },
|
|
13443
13436
|
...pluginsConfig,
|
|
@@ -13494,10 +13487,10 @@ class CapacitorApp {
|
|
|
13494
13487
|
constructor(app, target) {
|
|
13495
13488
|
this.app = app;
|
|
13496
13489
|
this.target = target;
|
|
13497
|
-
this.targetRootPath =
|
|
13498
|
-
this.targetRoot =
|
|
13499
|
-
this.targetWebRoot =
|
|
13500
|
-
this.targetAssetRoot =
|
|
13490
|
+
this.targetRootPath = path41.posix.join(".akan", "mobile", this.target.name);
|
|
13491
|
+
this.targetRoot = path41.join(this.app.cwdPath, this.targetRootPath);
|
|
13492
|
+
this.targetWebRoot = path41.join(this.targetRoot, "www");
|
|
13493
|
+
this.targetAssetRoot = path41.join(this.targetRoot, "assets");
|
|
13501
13494
|
this.project = new MobileProject(this.app.cwdPath, {
|
|
13502
13495
|
android: { path: this.androidRootPath },
|
|
13503
13496
|
ios: { path: this.iosProjectPath }
|
|
@@ -13512,9 +13505,9 @@ class CapacitorApp {
|
|
|
13512
13505
|
await mkdir11(this.targetRoot, { recursive: true });
|
|
13513
13506
|
if (regenerate) {
|
|
13514
13507
|
if (!platform || platform === "ios")
|
|
13515
|
-
await rm5(
|
|
13508
|
+
await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
|
|
13516
13509
|
if (!platform || platform === "android")
|
|
13517
|
-
await rm5(
|
|
13510
|
+
await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
|
|
13518
13511
|
}
|
|
13519
13512
|
const project = this.project;
|
|
13520
13513
|
await this.project.load();
|
|
@@ -13637,7 +13630,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13637
13630
|
const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
|
|
13638
13631
|
try {
|
|
13639
13632
|
await this.#spawn("xcodebuild", xcodebuildArgs, {
|
|
13640
|
-
cwd:
|
|
13633
|
+
cwd: path41.join(this.app.cwdPath, this.iosProjectPath),
|
|
13641
13634
|
env: mobileEnv
|
|
13642
13635
|
});
|
|
13643
13636
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
@@ -13662,7 +13655,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13662
13655
|
return isRecord2(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
13663
13656
|
}
|
|
13664
13657
|
async#getIosDevelopmentTeam() {
|
|
13665
|
-
const pbxprojPath =
|
|
13658
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
13666
13659
|
if (!await Bun.file(pbxprojPath).exists())
|
|
13667
13660
|
return;
|
|
13668
13661
|
return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
|
|
@@ -13688,7 +13681,7 @@ ${error.message}`;
|
|
|
13688
13681
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13689
13682
|
}
|
|
13690
13683
|
async#updateAndroidBuildTypes() {
|
|
13691
|
-
const appGradle = await FileEditor.create(
|
|
13684
|
+
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
13692
13685
|
const buildTypesBlock = `
|
|
13693
13686
|
debug {
|
|
13694
13687
|
applicationIdSuffix ".debug"
|
|
@@ -13732,7 +13725,7 @@ ${error.message}`;
|
|
|
13732
13725
|
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
13733
13726
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
13734
13727
|
stdio: "inherit",
|
|
13735
|
-
cwd:
|
|
13728
|
+
cwd: path41.join(this.app.cwdPath, this.androidRootPath),
|
|
13736
13729
|
env: await this.#commandEnv("release", env)
|
|
13737
13730
|
});
|
|
13738
13731
|
}
|
|
@@ -13740,10 +13733,10 @@ ${error.message}`;
|
|
|
13740
13733
|
await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
|
|
13741
13734
|
}
|
|
13742
13735
|
async#ensureAndroidAssetsDir() {
|
|
13743
|
-
await mkdir11(
|
|
13736
|
+
await mkdir11(path41.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
13744
13737
|
}
|
|
13745
13738
|
async#ensureAndroidDebugKeystore() {
|
|
13746
|
-
const keystorePath =
|
|
13739
|
+
const keystorePath = path41.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
13747
13740
|
if (await Bun.file(keystorePath).exists())
|
|
13748
13741
|
return;
|
|
13749
13742
|
await this.#spawn("keytool", [
|
|
@@ -13782,7 +13775,7 @@ ${error.message}`;
|
|
|
13782
13775
|
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
13783
13776
|
}
|
|
13784
13777
|
async#assertAndroidReleaseSigningConfig() {
|
|
13785
|
-
const gradlePropertiesPath =
|
|
13778
|
+
const gradlePropertiesPath = path41.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
|
|
13786
13779
|
const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
|
|
13787
13780
|
const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
|
|
13788
13781
|
if (missingKeys.length > 0)
|
|
@@ -13809,12 +13802,12 @@ ${error.message}`;
|
|
|
13809
13802
|
await this.#prepareAndroid({ operation: "release", env: "main" });
|
|
13810
13803
|
}
|
|
13811
13804
|
async prepareWww() {
|
|
13812
|
-
const htmlSource =
|
|
13805
|
+
const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
|
|
13813
13806
|
if (!await Bun.file(htmlSource).exists())
|
|
13814
13807
|
throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
|
|
13815
13808
|
await rm5(this.targetWebRoot, { recursive: true, force: true });
|
|
13816
13809
|
await mkdir11(this.targetWebRoot, { recursive: true });
|
|
13817
|
-
await Bun.write(
|
|
13810
|
+
await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
|
|
13818
13811
|
}
|
|
13819
13812
|
#injectMobileTargetMeta(html) {
|
|
13820
13813
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
@@ -13834,7 +13827,7 @@ ${error.message}`;
|
|
|
13834
13827
|
});
|
|
13835
13828
|
const content = `${JSON.stringify(config, null, 2)}
|
|
13836
13829
|
`;
|
|
13837
|
-
await Bun.write(
|
|
13830
|
+
await Bun.write(path41.join(this.targetRoot, "capacitor.config.json"), content);
|
|
13838
13831
|
return content;
|
|
13839
13832
|
}
|
|
13840
13833
|
async#prepareTargetAssets() {
|
|
@@ -13842,11 +13835,11 @@ ${error.message}`;
|
|
|
13842
13835
|
return;
|
|
13843
13836
|
await mkdir11(this.targetAssetRoot, { recursive: true });
|
|
13844
13837
|
if (this.target.assets.icon)
|
|
13845
|
-
await cp2(
|
|
13838
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.icon), path41.join(this.targetAssetRoot, "icon.png"), {
|
|
13846
13839
|
force: true
|
|
13847
13840
|
});
|
|
13848
13841
|
if (this.target.assets.splash)
|
|
13849
|
-
await cp2(
|
|
13842
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.splash), path41.join(this.targetAssetRoot, "splash.png"), {
|
|
13850
13843
|
force: true
|
|
13851
13844
|
});
|
|
13852
13845
|
}
|
|
@@ -13854,11 +13847,11 @@ ${error.message}`;
|
|
|
13854
13847
|
const files = this.target.files?.[platform];
|
|
13855
13848
|
if (!files)
|
|
13856
13849
|
return;
|
|
13857
|
-
const platformRoot =
|
|
13850
|
+
const platformRoot = path41.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
|
|
13858
13851
|
await Promise.all(Object.entries(files).map(async ([to, from]) => {
|
|
13859
|
-
const targetPath =
|
|
13860
|
-
await mkdir11(
|
|
13861
|
-
await cp2(
|
|
13852
|
+
const targetPath = path41.join(platformRoot, to);
|
|
13853
|
+
await mkdir11(path41.dirname(targetPath), { recursive: true });
|
|
13854
|
+
await cp2(path41.join(this.app.cwdPath, from), targetPath, { force: true });
|
|
13862
13855
|
}));
|
|
13863
13856
|
}
|
|
13864
13857
|
async#generateAssets({ operation, env }) {
|
|
@@ -13868,7 +13861,7 @@ ${error.message}`;
|
|
|
13868
13861
|
"@capacitor/assets",
|
|
13869
13862
|
"generate",
|
|
13870
13863
|
"--assetPath",
|
|
13871
|
-
|
|
13864
|
+
path41.posix.join(this.targetRootPath, "assets"),
|
|
13872
13865
|
"--iosProject",
|
|
13873
13866
|
this.iosProjectPath,
|
|
13874
13867
|
"--androidProject",
|
|
@@ -14078,7 +14071,7 @@ var Pkg = createInternalArgToken("Pkg");
|
|
|
14078
14071
|
var Module = createInternalArgToken("Module");
|
|
14079
14072
|
var Workspace = createInternalArgToken("Workspace");
|
|
14080
14073
|
// pkgs/@akanjs/devkit/commandDecorators/command.ts
|
|
14081
|
-
import
|
|
14074
|
+
import path42 from "path";
|
|
14082
14075
|
import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
|
|
14083
14076
|
import { Logger as Logger10 } from "akanjs/common";
|
|
14084
14077
|
import chalk6 from "chalk";
|
|
@@ -14582,7 +14575,7 @@ var runCommands = async (...commands) => {
|
|
|
14582
14575
|
process.exit(1);
|
|
14583
14576
|
});
|
|
14584
14577
|
const __dirname2 = getDirname(import.meta.url);
|
|
14585
|
-
const packageJsonCandidates = [`${
|
|
14578
|
+
const packageJsonCandidates = [`${path42.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
|
|
14586
14579
|
let cliPackageJson = null;
|
|
14587
14580
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
14588
14581
|
if (!await FileSys.fileExists(packageJsonPath))
|
|
@@ -14862,8 +14855,8 @@ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
|
|
|
14862
14855
|
return importSpecifiers;
|
|
14863
14856
|
};
|
|
14864
14857
|
var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
|
|
14865
|
-
const configFile = ts10.readConfigFile(tsConfigPath, (
|
|
14866
|
-
return ts10.sys.readFile(
|
|
14858
|
+
const configFile = ts10.readConfigFile(tsConfigPath, (path43) => {
|
|
14859
|
+
return ts10.sys.readFile(path43);
|
|
14867
14860
|
});
|
|
14868
14861
|
return ts10.parseJsonConfigFileContent(configFile.config, ts10.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
|
|
14869
14862
|
};
|
|
@@ -15079,6 +15072,571 @@ ${document}
|
|
|
15079
15072
|
`;
|
|
15080
15073
|
}
|
|
15081
15074
|
}
|
|
15075
|
+
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15076
|
+
import { createHash } from "crypto";
|
|
15077
|
+
import { readdir as readdir3, readFile as readFile2, stat as stat4 } from "fs/promises";
|
|
15078
|
+
import path43 from "path";
|
|
15079
|
+
import ignore2 from "ignore";
|
|
15080
|
+
import ts11 from "typescript";
|
|
15081
|
+
var MAX_FILE_LINES = 2000;
|
|
15082
|
+
var PLACEHOLDER_EXPORT_NAMES = new Set([
|
|
15083
|
+
"aa",
|
|
15084
|
+
"dumb",
|
|
15085
|
+
"dumb2",
|
|
15086
|
+
"someBaseLogic",
|
|
15087
|
+
"someCommonLogic",
|
|
15088
|
+
"someFrontendLogic",
|
|
15089
|
+
"someBackendLogic"
|
|
15090
|
+
]);
|
|
15091
|
+
var SUGGESTED_RULES = [
|
|
15092
|
+
"Keep generated scanSync index files out of hand-written changes; generated indexes should only contain one-depth export statements.",
|
|
15093
|
+
"Generated Akan index files should not contain placeholder exports such as aa, dumb, dumb2, or some*Logic.",
|
|
15094
|
+
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15095
|
+
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15096
|
+
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15097
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15098
|
+
"Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
|
|
15099
|
+
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15100
|
+
"Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
|
|
15101
|
+
"Move shared app utilities to apps/*/common instead of creating apps/*/base.",
|
|
15102
|
+
"Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
|
|
15103
|
+
];
|
|
15104
|
+
var APP_ROOT_FILES = new Set([
|
|
15105
|
+
"akan.app.json",
|
|
15106
|
+
"akan.config.ts",
|
|
15107
|
+
"capacitor.config.ts",
|
|
15108
|
+
"client.ts",
|
|
15109
|
+
"main.ts",
|
|
15110
|
+
"package.json",
|
|
15111
|
+
"server.ts",
|
|
15112
|
+
"tsconfig.json"
|
|
15113
|
+
]);
|
|
15114
|
+
var LIB_ROOT_FILES = new Set([
|
|
15115
|
+
"cnst.ts",
|
|
15116
|
+
"db.ts",
|
|
15117
|
+
"dict.ts",
|
|
15118
|
+
"option.ts",
|
|
15119
|
+
"sig.ts",
|
|
15120
|
+
"srv.ts",
|
|
15121
|
+
"st.ts",
|
|
15122
|
+
"useClient.ts",
|
|
15123
|
+
"useServer.ts"
|
|
15124
|
+
]);
|
|
15125
|
+
var CONVENTION_SUFFIXES = [
|
|
15126
|
+
".constant.ts",
|
|
15127
|
+
".dictionary.ts",
|
|
15128
|
+
".document.ts",
|
|
15129
|
+
".service.ts",
|
|
15130
|
+
".signal.ts",
|
|
15131
|
+
".store.ts"
|
|
15132
|
+
];
|
|
15133
|
+
|
|
15134
|
+
class AkanQualityScanner {
|
|
15135
|
+
async scan(workspaceRoot) {
|
|
15136
|
+
const targetFiles = await this.#collectTargetFiles(workspaceRoot);
|
|
15137
|
+
const sourceFiles = await Promise.all(targetFiles.map((file) => this.#readSourceFile(workspaceRoot, file)));
|
|
15138
|
+
const warnings = [
|
|
15139
|
+
...this.#scanGlobalQuality(sourceFiles),
|
|
15140
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanSingleFileQuality(sourceFile2)),
|
|
15141
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanConventionQuality(sourceFile2)),
|
|
15142
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanLayoutQuality(sourceFile2))
|
|
15143
|
+
];
|
|
15144
|
+
return {
|
|
15145
|
+
workspaceRoot,
|
|
15146
|
+
scannedFiles: sourceFiles.length,
|
|
15147
|
+
warnings: warnings.sort(compareWarnings),
|
|
15148
|
+
suggestedRules: SUGGESTED_RULES
|
|
15149
|
+
};
|
|
15150
|
+
}
|
|
15151
|
+
async#collectTargetFiles(workspaceRoot) {
|
|
15152
|
+
const ignoreFilter = ignore2().add(await this.#readGitIgnore(workspaceRoot));
|
|
15153
|
+
const files = [];
|
|
15154
|
+
for (const targetRoot of ["apps", "libs"]) {
|
|
15155
|
+
const absoluteTargetRoot = path43.join(workspaceRoot, targetRoot);
|
|
15156
|
+
if (!await isDirectory(absoluteTargetRoot))
|
|
15157
|
+
continue;
|
|
15158
|
+
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
15159
|
+
}
|
|
15160
|
+
return files.sort();
|
|
15161
|
+
}
|
|
15162
|
+
async#readGitIgnore(workspaceRoot) {
|
|
15163
|
+
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15164
|
+
if (!await Bun.file(gitIgnorePath).exists())
|
|
15165
|
+
return [];
|
|
15166
|
+
return (await readFile2(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15167
|
+
}
|
|
15168
|
+
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15169
|
+
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
15170
|
+
for (const entry of entries) {
|
|
15171
|
+
const absolutePath = path43.join(currentPath, entry.name);
|
|
15172
|
+
const relativePath = toPosix(path43.relative(workspaceRoot, absolutePath));
|
|
15173
|
+
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
15174
|
+
continue;
|
|
15175
|
+
if (entry.isDirectory()) {
|
|
15176
|
+
await this.#walkTargetFiles(workspaceRoot, absolutePath, ignoreFilter, files);
|
|
15177
|
+
continue;
|
|
15178
|
+
}
|
|
15179
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
15180
|
+
files.push(relativePath);
|
|
15181
|
+
}
|
|
15182
|
+
}
|
|
15183
|
+
}
|
|
15184
|
+
async#readSourceFile(workspaceRoot, file) {
|
|
15185
|
+
const absolutePath = path43.join(workspaceRoot, file);
|
|
15186
|
+
const content = await readFile2(absolutePath, "utf8");
|
|
15187
|
+
return {
|
|
15188
|
+
file,
|
|
15189
|
+
absolutePath,
|
|
15190
|
+
content,
|
|
15191
|
+
sourceFile: ts11.createSourceFile(file, content, ts11.ScriptTarget.Latest, true, getScriptKind(file))
|
|
15192
|
+
};
|
|
15193
|
+
}
|
|
15194
|
+
#scanGlobalQuality(sourceFiles) {
|
|
15195
|
+
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile2) => getExportedFunctionLikes(sourceFile2));
|
|
15196
|
+
const warnings = [];
|
|
15197
|
+
for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
|
|
15198
|
+
if (declarations.length < 2)
|
|
15199
|
+
continue;
|
|
15200
|
+
warnings.push({
|
|
15201
|
+
rule: "akan.global.duplicate-exported-function-name",
|
|
15202
|
+
scope: "global",
|
|
15203
|
+
severity: "warning",
|
|
15204
|
+
message: `Exported function or class name "${name}" is declared in ${declarations.length} files.`,
|
|
15205
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15206
|
+
});
|
|
15207
|
+
}
|
|
15208
|
+
const declarationsWithBody = exportedFunctionLikes.filter((declaration) => declaration.bodyFingerprint);
|
|
15209
|
+
for (const [fingerprint, declarations] of groupBy(declarationsWithBody, (declaration) => declaration.bodyFingerprint ?? "")) {
|
|
15210
|
+
const uniqueNames = new Set(declarations.map((declaration) => declaration.name));
|
|
15211
|
+
if (declarations.length < 2 || uniqueNames.size < 2 || fingerprint === "")
|
|
15212
|
+
continue;
|
|
15213
|
+
warnings.push({
|
|
15214
|
+
rule: "akan.global.duplicate-exported-function-body",
|
|
15215
|
+
scope: "global",
|
|
15216
|
+
severity: "warning",
|
|
15217
|
+
message: `Exported functions/classes share the same implementation body: ${declarations.map((declaration) => declaration.name).join(", ")}.`,
|
|
15218
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15219
|
+
});
|
|
15220
|
+
}
|
|
15221
|
+
return warnings;
|
|
15222
|
+
}
|
|
15223
|
+
#scanSingleFileQuality(sourceFile2) {
|
|
15224
|
+
const warnings = [];
|
|
15225
|
+
const lineCount = sourceFile2.content.split(/\r?\n/).length;
|
|
15226
|
+
const recommendedLineLimit = getRecommendedLineLimit(sourceFile2.file);
|
|
15227
|
+
if (recommendedLineLimit && lineCount > recommendedLineLimit) {
|
|
15228
|
+
warnings.push({
|
|
15229
|
+
rule: "akan.file.recommended-max-lines",
|
|
15230
|
+
scope: "file",
|
|
15231
|
+
severity: "warning",
|
|
15232
|
+
file: sourceFile2.file,
|
|
15233
|
+
message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
|
|
15234
|
+
});
|
|
15235
|
+
}
|
|
15236
|
+
if (lineCount > MAX_FILE_LINES) {
|
|
15237
|
+
warnings.push({
|
|
15238
|
+
rule: "akan.file.max-lines",
|
|
15239
|
+
scope: "file",
|
|
15240
|
+
severity: "warning",
|
|
15241
|
+
file: sourceFile2.file,
|
|
15242
|
+
message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
|
|
15243
|
+
});
|
|
15244
|
+
}
|
|
15245
|
+
warnings.push(...getPlaceholderExportWarnings(sourceFile2));
|
|
15246
|
+
warnings.push(...getDictionaryTextWarnings(sourceFile2));
|
|
15247
|
+
warnings.push(...getGlobalMutationWarnings(sourceFile2));
|
|
15248
|
+
const exportedClassNames = getExportedClassNames(sourceFile2.sourceFile);
|
|
15249
|
+
if (exportedClassNames.length === 0)
|
|
15250
|
+
return warnings;
|
|
15251
|
+
const allowedInterfaceNames = new Set(exportedClassNames.map((name) => `${name}Options`));
|
|
15252
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15253
|
+
if (declaration.kind === "class" && exportedClassNames.includes(declaration.name))
|
|
15254
|
+
continue;
|
|
15255
|
+
if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
|
|
15256
|
+
continue;
|
|
15257
|
+
warnings.push({
|
|
15258
|
+
rule: "akan.file.class-export-global-declaration",
|
|
15259
|
+
scope: "file",
|
|
15260
|
+
severity: "warning",
|
|
15261
|
+
file: sourceFile2.file,
|
|
15262
|
+
line: declaration.line,
|
|
15263
|
+
message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
|
|
15264
|
+
});
|
|
15265
|
+
}
|
|
15266
|
+
return warnings;
|
|
15267
|
+
}
|
|
15268
|
+
#scanConventionQuality(sourceFile2) {
|
|
15269
|
+
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile2.file.endsWith(candidate));
|
|
15270
|
+
if (!suffix)
|
|
15271
|
+
return [];
|
|
15272
|
+
const modelName = toPascalCase(path43.basename(sourceFile2.file, suffix));
|
|
15273
|
+
const warnings = [];
|
|
15274
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15275
|
+
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
15276
|
+
continue;
|
|
15277
|
+
warnings.push({
|
|
15278
|
+
rule: `akan.convention${suffix.replace(".ts", "")}`,
|
|
15279
|
+
scope: "convention",
|
|
15280
|
+
severity: "warning",
|
|
15281
|
+
file: sourceFile2.file,
|
|
15282
|
+
line: declaration.line,
|
|
15283
|
+
message: `${path43.basename(sourceFile2.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
15284
|
+
});
|
|
15285
|
+
}
|
|
15286
|
+
return warnings;
|
|
15287
|
+
}
|
|
15288
|
+
#scanLayoutQuality(sourceFile2) {
|
|
15289
|
+
const segments = sourceFile2.file.split("/");
|
|
15290
|
+
const warnings = [];
|
|
15291
|
+
if (segments[0] === "apps" && segments.length === 3 && !APP_ROOT_FILES.has(segments[2])) {
|
|
15292
|
+
warnings.push({
|
|
15293
|
+
rule: "akan.layout.app-root-file",
|
|
15294
|
+
scope: "layout",
|
|
15295
|
+
severity: "warning",
|
|
15296
|
+
file: sourceFile2.file,
|
|
15297
|
+
message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
|
|
15298
|
+
});
|
|
15299
|
+
}
|
|
15300
|
+
const libRootFile = getLibRootFile(sourceFile2.file);
|
|
15301
|
+
if (libRootFile && !LIB_ROOT_FILES.has(libRootFile)) {
|
|
15302
|
+
warnings.push({
|
|
15303
|
+
rule: "akan.layout.lib-root-file",
|
|
15304
|
+
scope: "layout",
|
|
15305
|
+
severity: "warning",
|
|
15306
|
+
file: sourceFile2.file,
|
|
15307
|
+
message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
|
|
15308
|
+
});
|
|
15309
|
+
}
|
|
15310
|
+
const moduleUiWarning = getModuleUiWarning(sourceFile2.file);
|
|
15311
|
+
if (moduleUiWarning)
|
|
15312
|
+
warnings.push(moduleUiWarning);
|
|
15313
|
+
return warnings;
|
|
15314
|
+
}
|
|
15315
|
+
}
|
|
15316
|
+
function formatQualityScanResult(result) {
|
|
15317
|
+
const sections = [
|
|
15318
|
+
"Akan Code Quality Scan",
|
|
15319
|
+
`workspace: ${result.workspaceRoot}`,
|
|
15320
|
+
`scanned files: ${result.scannedFiles}`,
|
|
15321
|
+
`warnings: ${result.warnings.length}`,
|
|
15322
|
+
"",
|
|
15323
|
+
"Warnings:",
|
|
15324
|
+
"",
|
|
15325
|
+
...formatQualityWarnings(result.warnings),
|
|
15326
|
+
"",
|
|
15327
|
+
"Suggested quality rules:",
|
|
15328
|
+
"",
|
|
15329
|
+
...result.suggestedRules.map((rule) => ` - ${rule}`)
|
|
15330
|
+
];
|
|
15331
|
+
return sections.join(`
|
|
15332
|
+
`);
|
|
15333
|
+
}
|
|
15334
|
+
function formatQualityWarnings(warnings) {
|
|
15335
|
+
if (warnings.length === 0)
|
|
15336
|
+
return ["No warnings found."];
|
|
15337
|
+
return warnings.flatMap((warning) => {
|
|
15338
|
+
const location = formatQualityLocation(warning.file, warning.line);
|
|
15339
|
+
const lines = [`${location} - warning ${warning.rule}: ${warning.message}`];
|
|
15340
|
+
if (warning.locations?.length) {
|
|
15341
|
+
lines.push(...warning.locations.map(({ file, line }) => ` note: related location ${formatQualityLocation(file, line)}`));
|
|
15342
|
+
}
|
|
15343
|
+
return lines;
|
|
15344
|
+
});
|
|
15345
|
+
}
|
|
15346
|
+
function formatQualityLocation(file, line) {
|
|
15347
|
+
return `${file ?? "<global>"}:${line ?? 1}:1`;
|
|
15348
|
+
}
|
|
15349
|
+
function getExportedFunctionLikes(sourceFile2) {
|
|
15350
|
+
const declarations = [];
|
|
15351
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15352
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15353
|
+
declarations.push({
|
|
15354
|
+
name: statement.name.text,
|
|
15355
|
+
kind: "function",
|
|
15356
|
+
file: sourceFile2.file,
|
|
15357
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15358
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement.body)
|
|
15359
|
+
});
|
|
15360
|
+
}
|
|
15361
|
+
if (ts11.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15362
|
+
declarations.push({
|
|
15363
|
+
name: statement.name.text,
|
|
15364
|
+
kind: "class",
|
|
15365
|
+
file: sourceFile2.file,
|
|
15366
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15367
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement)
|
|
15368
|
+
});
|
|
15369
|
+
}
|
|
15370
|
+
if (ts11.isVariableStatement(statement) && isExported(statement)) {
|
|
15371
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
15372
|
+
if (!ts11.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
15373
|
+
continue;
|
|
15374
|
+
declarations.push({
|
|
15375
|
+
name: declaration.name.text,
|
|
15376
|
+
kind: "function-variable",
|
|
15377
|
+
file: sourceFile2.file,
|
|
15378
|
+
line: getLine(sourceFile2.sourceFile, declaration),
|
|
15379
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, declaration.initializer)
|
|
15380
|
+
});
|
|
15381
|
+
}
|
|
15382
|
+
}
|
|
15383
|
+
}
|
|
15384
|
+
return declarations;
|
|
15385
|
+
}
|
|
15386
|
+
function getExportedClassNames(sourceFile2) {
|
|
15387
|
+
return sourceFile2.statements.filter((statement) => ts11.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
|
|
15388
|
+
}
|
|
15389
|
+
function getPlaceholderExportWarnings(sourceFile2) {
|
|
15390
|
+
if (!sourceFile2.file.endsWith("/index.ts") && !sourceFile2.file.endsWith("/index.tsx"))
|
|
15391
|
+
return [];
|
|
15392
|
+
return getTopLevelDeclarations(sourceFile2).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
|
|
15393
|
+
rule: "akan.file.placeholder-export",
|
|
15394
|
+
scope: "file",
|
|
15395
|
+
severity: "warning",
|
|
15396
|
+
file: sourceFile2.file,
|
|
15397
|
+
line: declaration.line,
|
|
15398
|
+
message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
|
|
15399
|
+
}));
|
|
15400
|
+
}
|
|
15401
|
+
function getDictionaryTextWarnings(sourceFile2) {
|
|
15402
|
+
if (!sourceFile2.file.endsWith(".dictionary.ts"))
|
|
15403
|
+
return [];
|
|
15404
|
+
const stalePatterns = [
|
|
15405
|
+
{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
|
|
15406
|
+
{ pattern: /settting/, label: "misspelling: settting" },
|
|
15407
|
+
{ pattern: /\uBC30\uB108 \uC218/, label: "stale copied Korean domain noun: \uBC30\uB108 \uC218" }
|
|
15408
|
+
];
|
|
15409
|
+
return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile2.content, pattern).map((line) => ({
|
|
15410
|
+
rule: "akan.file.dictionary-stale-text",
|
|
15411
|
+
scope: "file",
|
|
15412
|
+
severity: "warning",
|
|
15413
|
+
file: sourceFile2.file,
|
|
15414
|
+
line,
|
|
15415
|
+
message: `Dictionary text appears to contain ${label}.`
|
|
15416
|
+
})));
|
|
15417
|
+
}
|
|
15418
|
+
function getGlobalMutationWarnings(sourceFile2) {
|
|
15419
|
+
const warnings = [];
|
|
15420
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15421
|
+
if (ts11.isModuleDeclaration(statement) && statement.name.getText(sourceFile2.sourceFile) === "global") {
|
|
15422
|
+
warnings.push({
|
|
15423
|
+
rule: "akan.file.global-declaration",
|
|
15424
|
+
scope: "file",
|
|
15425
|
+
severity: "warning",
|
|
15426
|
+
file: sourceFile2.file,
|
|
15427
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15428
|
+
message: "Global declarations require an explicit low-level integration allowlist."
|
|
15429
|
+
});
|
|
15430
|
+
}
|
|
15431
|
+
if (ts11.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
15432
|
+
warnings.push({
|
|
15433
|
+
rule: "akan.file.window-augmentation",
|
|
15434
|
+
scope: "file",
|
|
15435
|
+
severity: "warning",
|
|
15436
|
+
file: sourceFile2.file,
|
|
15437
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15438
|
+
message: "Window augmentation should be isolated to approved browser integration files."
|
|
15439
|
+
});
|
|
15440
|
+
}
|
|
15441
|
+
if (ts11.isExpressionStatement(statement) && statement.expression.getText(sourceFile2.sourceFile).includes(".prototype.")) {
|
|
15442
|
+
warnings.push({
|
|
15443
|
+
rule: "akan.file.prototype-mutation",
|
|
15444
|
+
scope: "file",
|
|
15445
|
+
severity: "warning",
|
|
15446
|
+
file: sourceFile2.file,
|
|
15447
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15448
|
+
message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
|
|
15449
|
+
});
|
|
15450
|
+
}
|
|
15451
|
+
}
|
|
15452
|
+
return warnings;
|
|
15453
|
+
}
|
|
15454
|
+
function getTopLevelDeclarations(sourceFile2) {
|
|
15455
|
+
return sourceFile2.sourceFile.statements.flatMap((statement) => getTopLevelDeclaration(sourceFile2.sourceFile, statement));
|
|
15456
|
+
}
|
|
15457
|
+
function getTopLevelDeclaration(sourceFile2, statement) {
|
|
15458
|
+
const line = getLine(sourceFile2, statement);
|
|
15459
|
+
if (ts11.isClassDeclaration(statement) && statement.name) {
|
|
15460
|
+
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
15461
|
+
}
|
|
15462
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name) {
|
|
15463
|
+
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
15464
|
+
}
|
|
15465
|
+
if (ts11.isInterfaceDeclaration(statement)) {
|
|
15466
|
+
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
15467
|
+
}
|
|
15468
|
+
if (ts11.isTypeAliasDeclaration(statement)) {
|
|
15469
|
+
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
15470
|
+
}
|
|
15471
|
+
if (ts11.isEnumDeclaration(statement)) {
|
|
15472
|
+
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
15473
|
+
}
|
|
15474
|
+
if (ts11.isVariableStatement(statement)) {
|
|
15475
|
+
return statement.declarationList.declarations.filter((declaration) => ts11.isIdentifier(declaration.name)).map((declaration) => ({
|
|
15476
|
+
name: declaration.name.text,
|
|
15477
|
+
kind: "variable",
|
|
15478
|
+
line: getLine(sourceFile2, declaration),
|
|
15479
|
+
exported: isExported(statement),
|
|
15480
|
+
node: statement
|
|
15481
|
+
}));
|
|
15482
|
+
}
|
|
15483
|
+
if (ts11.isExportDeclaration(statement)) {
|
|
15484
|
+
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
15485
|
+
}
|
|
15486
|
+
return [];
|
|
15487
|
+
}
|
|
15488
|
+
function isAllowedConventionDeclaration(suffix, modelName, declaration) {
|
|
15489
|
+
if (suffix === ".dictionary.ts")
|
|
15490
|
+
return isExportedConst(declaration) && declaration.name === "dictionary";
|
|
15491
|
+
if (suffix === ".constant.ts")
|
|
15492
|
+
return isAllowedConstantDeclaration(modelName, declaration);
|
|
15493
|
+
if (suffix === ".document.ts")
|
|
15494
|
+
return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
|
|
15495
|
+
if (suffix === ".service.ts")
|
|
15496
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Service`;
|
|
15497
|
+
if (suffix === ".signal.ts")
|
|
15498
|
+
return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
|
|
15499
|
+
if (suffix === ".store.ts")
|
|
15500
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Store`;
|
|
15501
|
+
return false;
|
|
15502
|
+
}
|
|
15503
|
+
function isAllowedConstantDeclaration(modelName, declaration) {
|
|
15504
|
+
if (declaration.kind !== "class")
|
|
15505
|
+
return false;
|
|
15506
|
+
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
15507
|
+
return true;
|
|
15508
|
+
if (!ts11.isClassDeclaration(declaration.node))
|
|
15509
|
+
return false;
|
|
15510
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts11.SyntaxKind.ExtendsKeyword);
|
|
15511
|
+
const expression = heritageClause?.types[0]?.expression;
|
|
15512
|
+
return !!expression && expression.getText().startsWith("enumOf(");
|
|
15513
|
+
}
|
|
15514
|
+
function getConventionDescription(suffix, modelName) {
|
|
15515
|
+
if (suffix === ".dictionary.ts")
|
|
15516
|
+
return "export const dictionary";
|
|
15517
|
+
if (suffix === ".constant.ts")
|
|
15518
|
+
return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
|
|
15519
|
+
if (suffix === ".document.ts")
|
|
15520
|
+
return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
|
|
15521
|
+
if (suffix === ".service.ts")
|
|
15522
|
+
return `${modelName}Service`;
|
|
15523
|
+
if (suffix === ".signal.ts")
|
|
15524
|
+
return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
|
|
15525
|
+
return `${modelName}Store`;
|
|
15526
|
+
}
|
|
15527
|
+
function getLibRootFile(file) {
|
|
15528
|
+
const segments = file.split("/");
|
|
15529
|
+
if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
|
|
15530
|
+
return segments[3];
|
|
15531
|
+
if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
|
|
15532
|
+
return segments[4];
|
|
15533
|
+
return null;
|
|
15534
|
+
}
|
|
15535
|
+
function getModuleUiWarning(file) {
|
|
15536
|
+
if (!file.endsWith(".tsx"))
|
|
15537
|
+
return null;
|
|
15538
|
+
const moduleInfo = getModuleInfo(file);
|
|
15539
|
+
if (!moduleInfo)
|
|
15540
|
+
return null;
|
|
15541
|
+
const { moduleName, fileName, kind } = moduleInfo;
|
|
15542
|
+
const pascalName = toPascalCase(moduleName.replace(/^_+/, ""));
|
|
15543
|
+
const allowedSuffixes = kind === "database" ? ["Template", "Unit", "Util", "View", "Zone"] : kind === "service" ? ["Util", "Zone"] : ["Template", "Unit"];
|
|
15544
|
+
const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
|
|
15545
|
+
if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
|
|
15546
|
+
return null;
|
|
15547
|
+
return {
|
|
15548
|
+
rule: "akan.layout.module-ui-file",
|
|
15549
|
+
scope: "layout",
|
|
15550
|
+
severity: "warning",
|
|
15551
|
+
file,
|
|
15552
|
+
message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
|
|
15553
|
+
};
|
|
15554
|
+
}
|
|
15555
|
+
function getModuleInfo(file) {
|
|
15556
|
+
const segments = file.split("/");
|
|
15557
|
+
const libIndex = segments.indexOf("lib");
|
|
15558
|
+
if (libIndex < 0)
|
|
15559
|
+
return null;
|
|
15560
|
+
const moduleName = segments[libIndex + 1];
|
|
15561
|
+
if (moduleName === "__scalar") {
|
|
15562
|
+
if (segments.length !== libIndex + 4)
|
|
15563
|
+
return null;
|
|
15564
|
+
return { moduleName: segments[libIndex + 2], fileName: segments[libIndex + 3], kind: "scalar" };
|
|
15565
|
+
}
|
|
15566
|
+
if (segments.length !== libIndex + 3)
|
|
15567
|
+
return null;
|
|
15568
|
+
const fileName = segments[libIndex + 2];
|
|
15569
|
+
if (moduleName.startsWith("__")) {
|
|
15570
|
+
const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
|
|
15571
|
+
return { moduleName: scalarName, fileName, kind: "scalar" };
|
|
15572
|
+
}
|
|
15573
|
+
if (moduleName.startsWith("_"))
|
|
15574
|
+
return { moduleName, fileName, kind: "service" };
|
|
15575
|
+
return { moduleName, fileName, kind: "database" };
|
|
15576
|
+
}
|
|
15577
|
+
function isExportedConst(declaration) {
|
|
15578
|
+
return declaration.exported && ts11.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts11.NodeFlags.Const) !== 0;
|
|
15579
|
+
}
|
|
15580
|
+
function isExported(node) {
|
|
15581
|
+
return !!ts11.getCombinedModifierFlags(node) && !!(ts11.getCombinedModifierFlags(node) & ts11.ModifierFlags.Export);
|
|
15582
|
+
}
|
|
15583
|
+
function isFunctionLikeInitializer(node) {
|
|
15584
|
+
return !!node && (ts11.isArrowFunction(node) || ts11.isFunctionExpression(node));
|
|
15585
|
+
}
|
|
15586
|
+
function getBodyFingerprint(sourceFile2, node) {
|
|
15587
|
+
if (!node)
|
|
15588
|
+
return;
|
|
15589
|
+
const normalizedBody = node.getText(sourceFile2).replace(/\s+/g, " ").trim();
|
|
15590
|
+
if (normalizedBody.length < 80)
|
|
15591
|
+
return;
|
|
15592
|
+
return createHash("sha256").update(normalizedBody).digest("hex");
|
|
15593
|
+
}
|
|
15594
|
+
function shouldSkipPath(relativePath, isDirectory, ignoreFilter) {
|
|
15595
|
+
const ignorePath = isDirectory ? `${relativePath}/` : relativePath;
|
|
15596
|
+
return relativePath === ".git" || relativePath.includes("/.git/") || relativePath.includes("/node_modules/") || ignoreFilter.ignores(relativePath) || ignoreFilter.ignores(ignorePath);
|
|
15597
|
+
}
|
|
15598
|
+
async function isDirectory(absolutePath) {
|
|
15599
|
+
try {
|
|
15600
|
+
return (await stat4(absolutePath)).isDirectory();
|
|
15601
|
+
} catch {
|
|
15602
|
+
return false;
|
|
15603
|
+
}
|
|
15604
|
+
}
|
|
15605
|
+
function getScriptKind(file) {
|
|
15606
|
+
return file.endsWith(".tsx") ? ts11.ScriptKind.TSX : ts11.ScriptKind.TS;
|
|
15607
|
+
}
|
|
15608
|
+
function getLine(sourceFile2, node) {
|
|
15609
|
+
return sourceFile2.getLineAndCharacterOfPosition(node.getStart(sourceFile2)).line + 1;
|
|
15610
|
+
}
|
|
15611
|
+
function getRecommendedLineLimit(file) {
|
|
15612
|
+
if (file.endsWith(".service.ts"))
|
|
15613
|
+
return 500;
|
|
15614
|
+
if (file.endsWith(".Template.tsx") || file.endsWith(".Zone.tsx"))
|
|
15615
|
+
return 800;
|
|
15616
|
+
if (file.endsWith(".Util.tsx"))
|
|
15617
|
+
return 1000;
|
|
15618
|
+
return null;
|
|
15619
|
+
}
|
|
15620
|
+
function findPatternLines(content, pattern) {
|
|
15621
|
+
return content.split(/\r?\n/).flatMap((line, index) => pattern.test(line) ? [index + 1] : []);
|
|
15622
|
+
}
|
|
15623
|
+
function toPascalCase(value) {
|
|
15624
|
+
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
15625
|
+
}
|
|
15626
|
+
function toPosix(value) {
|
|
15627
|
+
return value.split(path43.sep).join("/");
|
|
15628
|
+
}
|
|
15629
|
+
function groupBy(items, getKey) {
|
|
15630
|
+
const grouped = new Map;
|
|
15631
|
+
for (const item of items) {
|
|
15632
|
+
const key = getKey(item);
|
|
15633
|
+
grouped.set(key, [...grouped.get(key) ?? [], item]);
|
|
15634
|
+
}
|
|
15635
|
+
return grouped;
|
|
15636
|
+
}
|
|
15637
|
+
function compareWarnings(a, b) {
|
|
15638
|
+
return a.scope.localeCompare(b.scope) || (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || a.rule.localeCompare(b.rule);
|
|
15639
|
+
}
|
|
15082
15640
|
// pkgs/@akanjs/devkit/selectModel.ts
|
|
15083
15641
|
import { select as select5 } from "@inquirer/prompts";
|
|
15084
15642
|
// pkgs/@akanjs/devkit/streamAi.ts
|
|
@@ -15196,10 +15754,10 @@ class IncrementalBuilder {
|
|
|
15196
15754
|
}
|
|
15197
15755
|
}
|
|
15198
15756
|
batchTouchesPagesTree(appDir, batch) {
|
|
15199
|
-
const absAppDir =
|
|
15757
|
+
const absAppDir = path44.resolve(appDir);
|
|
15200
15758
|
for (const f of batch.files) {
|
|
15201
|
-
const abs =
|
|
15202
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
15759
|
+
const abs = path44.resolve(f);
|
|
15760
|
+
if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
|
|
15203
15761
|
continue;
|
|
15204
15762
|
if (/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
15205
15763
|
return true;
|
|
@@ -15207,15 +15765,15 @@ class IncrementalBuilder {
|
|
|
15207
15765
|
return false;
|
|
15208
15766
|
}
|
|
15209
15767
|
async batchMayChangePageKeys(appDir, batch) {
|
|
15210
|
-
const absAppDir =
|
|
15211
|
-
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) =>
|
|
15768
|
+
const absAppDir = path44.resolve(appDir);
|
|
15769
|
+
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path44.normalize(key)));
|
|
15212
15770
|
for (const f of batch.files) {
|
|
15213
|
-
const abs =
|
|
15214
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
15771
|
+
const abs = path44.resolve(f);
|
|
15772
|
+
if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
|
|
15215
15773
|
continue;
|
|
15216
15774
|
if (!/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
15217
15775
|
continue;
|
|
15218
|
-
const rel =
|
|
15776
|
+
const rel = path44.normalize(path44.relative(absAppDir, abs));
|
|
15219
15777
|
if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
|
|
15220
15778
|
return true;
|
|
15221
15779
|
}
|
|
@@ -15243,7 +15801,7 @@ class IncrementalBuilder {
|
|
|
15243
15801
|
${cssText}`).toString(36);
|
|
15244
15802
|
const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
|
|
15245
15803
|
const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
|
|
15246
|
-
await Bun.write(
|
|
15804
|
+
await Bun.write(path44.join(artifactDir, cssRelPath), cssText);
|
|
15247
15805
|
cssAssetEntries.push([basePath2, { cssUrl, cssRelPath }]);
|
|
15248
15806
|
cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
|
|
15249
15807
|
})()
|
|
@@ -15311,10 +15869,10 @@ ${cssText}`).toString(36);
|
|
|
15311
15869
|
if (changedFiles.length === 0)
|
|
15312
15870
|
return false;
|
|
15313
15871
|
return changedFiles.some((file) => {
|
|
15314
|
-
const normalized =
|
|
15872
|
+
const normalized = path44.resolve(file);
|
|
15315
15873
|
if (/\.(woff2?|ttf|otf)$/i.test(normalized))
|
|
15316
15874
|
return true;
|
|
15317
|
-
return this.#optimizedFonts.files.some((fontFile) =>
|
|
15875
|
+
return this.#optimizedFonts.files.some((fontFile) => path44.resolve(fontFile) === normalized);
|
|
15318
15876
|
});
|
|
15319
15877
|
}
|
|
15320
15878
|
async installWatcher() {
|