@akanjs/cli 2.3.9-rc.9 → 2.3.10-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/incrementalBuilder.proc.js +849 -99
- package/index.js +993 -147
- package/package.json +2 -2
- package/templates/workspaceRoot/CLAUDE.md.template +3 -0
package/index.js
CHANGED
|
@@ -742,6 +742,44 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
742
742
|
fetchTimeoutMs: 7000,
|
|
743
743
|
maxRemoteBytes: 25 * 1024 * 1024
|
|
744
744
|
};
|
|
745
|
+
var normalizeIndexPath = (indexPath) => {
|
|
746
|
+
const normalized = indexPath?.trim();
|
|
747
|
+
if (!normalized)
|
|
748
|
+
return;
|
|
749
|
+
const path2 = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
750
|
+
return path2 === "/" ? "/" : path2;
|
|
751
|
+
};
|
|
752
|
+
var normalizeStringList = (values) => {
|
|
753
|
+
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
754
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
755
|
+
};
|
|
756
|
+
var normalizeDeepLinkDomain = (domain) => {
|
|
757
|
+
const normalized = domain.trim();
|
|
758
|
+
if (!normalized)
|
|
759
|
+
return "";
|
|
760
|
+
try {
|
|
761
|
+
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
762
|
+
return url.host.toLowerCase();
|
|
763
|
+
} catch {
|
|
764
|
+
return normalized.replace(/^https?:\/\//, "").replace(/\/+$/g, "").toLowerCase();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
var normalizeDeepLinks = (deepLinks) => {
|
|
768
|
+
if (!deepLinks)
|
|
769
|
+
return;
|
|
770
|
+
const schemes = normalizeStringList(deepLinks.schemes);
|
|
771
|
+
const domains = normalizeStringList(deepLinks.domains?.map(normalizeDeepLinkDomain));
|
|
772
|
+
const teamId = deepLinks.ios?.teamId?.trim();
|
|
773
|
+
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints);
|
|
774
|
+
if (!schemes && !domains && !teamId && !sha256CertFingerprints)
|
|
775
|
+
return;
|
|
776
|
+
return {
|
|
777
|
+
...schemes ? { schemes } : {},
|
|
778
|
+
...domains ? { domains } : {},
|
|
779
|
+
...teamId ? { ios: { teamId } } : {},
|
|
780
|
+
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
781
|
+
};
|
|
782
|
+
};
|
|
745
783
|
|
|
746
784
|
class AkanAppConfig {
|
|
747
785
|
app;
|
|
@@ -755,6 +793,7 @@ class AkanAppConfig {
|
|
|
755
793
|
i18n;
|
|
756
794
|
publicEnv;
|
|
757
795
|
mobile;
|
|
796
|
+
secrets;
|
|
758
797
|
baseDevEnv;
|
|
759
798
|
libs;
|
|
760
799
|
domains = new Set;
|
|
@@ -781,11 +820,16 @@ class AkanAppConfig {
|
|
|
781
820
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
782
821
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
783
822
|
this.publicEnv = config?.publicEnv ?? [];
|
|
823
|
+
this.secrets = config?.secrets ?? [];
|
|
784
824
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
785
825
|
this.docker = this.#makeDockerContent(config?.docker ?? {});
|
|
786
826
|
}
|
|
787
827
|
#resolveMobileConfig(mobile) {
|
|
788
|
-
const {
|
|
828
|
+
const {
|
|
829
|
+
targets: rawTargets,
|
|
830
|
+
indexPath: _indexPath,
|
|
831
|
+
...rawMobile
|
|
832
|
+
} = mobile ?? {};
|
|
789
833
|
const appName = rawMobile.appName ?? this.app.name;
|
|
790
834
|
const appId = rawMobile.appId ?? `com.${this.app.name}.app`;
|
|
791
835
|
const version = rawMobile.version ?? "0.0.1";
|
|
@@ -798,6 +842,8 @@ class AkanAppConfig {
|
|
|
798
842
|
const target = rawTarget;
|
|
799
843
|
const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
|
|
800
844
|
const basePath2 = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
845
|
+
const indexPath = normalizeIndexPath(target.indexPath);
|
|
846
|
+
const deepLinks = normalizeDeepLinks(target.deepLinks);
|
|
801
847
|
if (basePath2 && !this.basePaths.has(basePath2)) {
|
|
802
848
|
throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath2}' in apps/${this.app.name}/akan.config.ts`);
|
|
803
849
|
}
|
|
@@ -806,6 +852,8 @@ class AkanAppConfig {
|
|
|
806
852
|
...target,
|
|
807
853
|
name,
|
|
808
854
|
basePath: basePath2,
|
|
855
|
+
indexPath,
|
|
856
|
+
deepLinks,
|
|
809
857
|
appName: target.appName ?? appName,
|
|
810
858
|
appId: target.appId ?? appId,
|
|
811
859
|
version: target.version ?? version,
|
|
@@ -12114,7 +12162,14 @@ class SsrBaseArtifactBuilder {
|
|
|
12114
12162
|
basePaths: [...akanConfig2.basePaths],
|
|
12115
12163
|
branches: [...akanConfig2.branches],
|
|
12116
12164
|
i18n: akanConfig2.i18n,
|
|
12117
|
-
imageConfig: akanConfig2.images
|
|
12165
|
+
imageConfig: akanConfig2.images,
|
|
12166
|
+
deepLinkAssociations: Object.values(akanConfig2.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
12167
|
+
targetName: target.name,
|
|
12168
|
+
appId: target.appId,
|
|
12169
|
+
domains: target.deepLinks?.domains ?? [],
|
|
12170
|
+
iosTeamId: target.deepLinks?.ios?.teamId,
|
|
12171
|
+
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
12172
|
+
}))
|
|
12118
12173
|
};
|
|
12119
12174
|
await Bun.write(path35.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
12120
12175
|
`);
|
|
@@ -12502,6 +12557,7 @@ void run().catch((error) => {
|
|
|
12502
12557
|
}
|
|
12503
12558
|
// pkgs/@akanjs/devkit/applicationReleasePackager.ts
|
|
12504
12559
|
import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
|
|
12560
|
+
import path38 from "path";
|
|
12505
12561
|
|
|
12506
12562
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
12507
12563
|
import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
|
|
@@ -12617,13 +12673,9 @@ class ApplicationReleasePackager {
|
|
|
12617
12673
|
await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
|
|
12618
12674
|
await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
|
|
12619
12675
|
await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
"-C",
|
|
12624
|
-
buildRoot,
|
|
12625
|
-
"./"
|
|
12626
|
-
]);
|
|
12676
|
+
const releaseRoot = this.#app.workspace.workspaceRoot;
|
|
12677
|
+
const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
|
|
12678
|
+
await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
|
|
12627
12679
|
await this.#writeCsrZipIfPresent();
|
|
12628
12680
|
return { platformVersion };
|
|
12629
12681
|
}
|
|
@@ -12644,22 +12696,18 @@ class ApplicationReleasePackager {
|
|
|
12644
12696
|
await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
|
|
12645
12697
|
const libDeps = ["social", "shared", "platform", "util"];
|
|
12646
12698
|
await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
|
|
12647
|
-
await Promise.all([".next", "ios", "android", "public/libs"].map(async (
|
|
12648
|
-
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${
|
|
12699
|
+
await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
|
|
12700
|
+
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
|
|
12649
12701
|
if (await FileSys.dirExists(targetPath))
|
|
12650
12702
|
await rm4(targetPath, { recursive: true, force: true });
|
|
12651
12703
|
}));
|
|
12652
12704
|
const syncPaths = [".husky", ".gitignore", "package.json"];
|
|
12653
|
-
await Promise.all(syncPaths.map((
|
|
12705
|
+
await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
|
|
12654
12706
|
await this.#writeSourceTsconfig(sourceRoot, libDeps);
|
|
12655
12707
|
await Bun.write(`${sourceRoot}/README.md`, readme);
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
"-C",
|
|
12660
|
-
sourceRoot,
|
|
12661
|
-
"./"
|
|
12662
|
-
]);
|
|
12708
|
+
const sourceCwd = this.#app.workspace.cwdPath;
|
|
12709
|
+
const sourceArchivePath = path38.relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`).split(path38.sep).join("/");
|
|
12710
|
+
await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
|
|
12663
12711
|
}
|
|
12664
12712
|
async#resetSourceRoot(sourceRoot) {
|
|
12665
12713
|
if (await FileSys.dirExists(sourceRoot)) {
|
|
@@ -12741,20 +12789,20 @@ class ApplicationReleasePackager {
|
|
|
12741
12789
|
}
|
|
12742
12790
|
}
|
|
12743
12791
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
12744
|
-
import
|
|
12792
|
+
import path39 from "path";
|
|
12745
12793
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
12746
12794
|
async function resolveSignalTestPreloadPath(target) {
|
|
12747
12795
|
const candidates = [];
|
|
12748
12796
|
const addResolvedPackageCandidate = (basePath2) => {
|
|
12749
12797
|
try {
|
|
12750
|
-
candidates.push(
|
|
12798
|
+
candidates.push(path39.join(path39.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
|
|
12751
12799
|
} catch {}
|
|
12752
12800
|
};
|
|
12753
12801
|
addResolvedPackageCandidate(target.cwdPath);
|
|
12754
12802
|
addResolvedPackageCandidate(process.cwd());
|
|
12755
|
-
addResolvedPackageCandidate(
|
|
12803
|
+
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12756
12804
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12757
|
-
candidates.push(
|
|
12805
|
+
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));
|
|
12758
12806
|
for (const candidate of [...new Set(candidates)]) {
|
|
12759
12807
|
if (await Bun.file(candidate).exists())
|
|
12760
12808
|
return candidate;
|
|
@@ -12764,7 +12812,7 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12764
12812
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12765
12813
|
import { existsSync as existsSync2 } from "fs";
|
|
12766
12814
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
12767
|
-
import
|
|
12815
|
+
import path40 from "path";
|
|
12768
12816
|
var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
|
|
12769
12817
|
var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
|
|
12770
12818
|
var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
|
|
@@ -12781,14 +12829,14 @@ class Builder {
|
|
|
12781
12829
|
#globEntrypoints(cwd, pattern) {
|
|
12782
12830
|
const glob = new Bun.Glob(pattern);
|
|
12783
12831
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12784
|
-
const segments = relativePath.split(
|
|
12832
|
+
const segments = relativePath.split(path40.sep);
|
|
12785
12833
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12786
|
-
}).map((rel) =>
|
|
12834
|
+
}).map((rel) => path40.join(cwd, rel));
|
|
12787
12835
|
}
|
|
12788
12836
|
#globFiles(cwd, pattern = "**/*.*") {
|
|
12789
12837
|
const glob = new Bun.Glob(pattern);
|
|
12790
12838
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12791
|
-
const segments = relativePath.split(
|
|
12839
|
+
const segments = relativePath.split(path40.sep);
|
|
12792
12840
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12793
12841
|
});
|
|
12794
12842
|
}
|
|
@@ -12796,17 +12844,17 @@ class Builder {
|
|
|
12796
12844
|
const out = [];
|
|
12797
12845
|
for (const p of additionalEntryPoints) {
|
|
12798
12846
|
if (p.includes("*")) {
|
|
12799
|
-
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${
|
|
12847
|
+
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path40.sep}`) ? p.slice(cwd.length + 1) : p;
|
|
12800
12848
|
out.push(...this.#globEntrypoints(cwd, rel));
|
|
12801
12849
|
} else
|
|
12802
|
-
out.push(
|
|
12850
|
+
out.push(path40.isAbsolute(p) ? p : path40.join(cwd, p));
|
|
12803
12851
|
}
|
|
12804
12852
|
return out;
|
|
12805
12853
|
}
|
|
12806
12854
|
#getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
|
|
12807
12855
|
const cwd = this.#executor.cwdPath;
|
|
12808
12856
|
const entrypoints = [
|
|
12809
|
-
...bundle ? [
|
|
12857
|
+
...bundle ? [path40.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
|
|
12810
12858
|
...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
|
|
12811
12859
|
];
|
|
12812
12860
|
return {
|
|
@@ -12827,9 +12875,9 @@ class Builder {
|
|
|
12827
12875
|
for (const relativePath of this.#globFiles(cwd)) {
|
|
12828
12876
|
if (relativePath === "package.json")
|
|
12829
12877
|
continue;
|
|
12830
|
-
const sourcePath =
|
|
12831
|
-
const targetPath =
|
|
12832
|
-
await mkdir10(
|
|
12878
|
+
const sourcePath = path40.join(cwd, relativePath);
|
|
12879
|
+
const targetPath = path40.join(this.#distExecutor.cwdPath, relativePath);
|
|
12880
|
+
await mkdir10(path40.dirname(targetPath), { recursive: true });
|
|
12833
12881
|
await Bun.write(targetPath, Bun.file(sourcePath));
|
|
12834
12882
|
}
|
|
12835
12883
|
}
|
|
@@ -12840,13 +12888,13 @@ class Builder {
|
|
|
12840
12888
|
return withoutFormatDir;
|
|
12841
12889
|
if (!hasDotSlash && withoutFormatDir === publishedPath)
|
|
12842
12890
|
return publishedPath;
|
|
12843
|
-
const parsed =
|
|
12891
|
+
const parsed = path40.posix.parse(withoutFormatDir);
|
|
12844
12892
|
if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
|
|
12845
12893
|
return withoutFormatDir;
|
|
12846
|
-
const withoutExt =
|
|
12894
|
+
const withoutExt = path40.posix.join(parsed.dir, parsed.name);
|
|
12847
12895
|
const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
|
|
12848
12896
|
const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
|
|
12849
|
-
const matchedSource = sourceCandidates.find((candidate) => existsSync2(
|
|
12897
|
+
const matchedSource = sourceCandidates.find((candidate) => existsSync2(path40.join(this.#executor.cwdPath, candidate)));
|
|
12850
12898
|
if (!matchedSource)
|
|
12851
12899
|
return withoutFormatDir;
|
|
12852
12900
|
return hasDotSlash ? `./${matchedSource}` : matchedSource;
|
|
@@ -12895,9 +12943,9 @@ class Builder {
|
|
|
12895
12943
|
}
|
|
12896
12944
|
}
|
|
12897
12945
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12898
|
-
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12946
|
+
import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
|
|
12899
12947
|
import os from "os";
|
|
12900
|
-
import
|
|
12948
|
+
import path41 from "path";
|
|
12901
12949
|
import { select as select2 } from "@inquirer/prompts";
|
|
12902
12950
|
import { MobileProject } from "@trapezedev/project";
|
|
12903
12951
|
import { capitalize as capitalize5 } from "akanjs/common";
|
|
@@ -13104,13 +13152,13 @@ var rootCapacitorConfigFilenames = [
|
|
|
13104
13152
|
"capacitor.config.js",
|
|
13105
13153
|
"capacitor.config.json"
|
|
13106
13154
|
];
|
|
13107
|
-
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) =>
|
|
13155
|
+
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
|
|
13108
13156
|
async function clearRootCapacitorConfigs(appRoot) {
|
|
13109
13157
|
await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
|
|
13110
13158
|
}
|
|
13111
13159
|
async function writeRootCapacitorConfig(appRoot, content) {
|
|
13112
13160
|
await clearRootCapacitorConfigs(appRoot);
|
|
13113
|
-
await Bun.write(
|
|
13161
|
+
await Bun.write(path41.join(appRoot, "capacitor.config.json"), content);
|
|
13114
13162
|
}
|
|
13115
13163
|
var getLocalIP = () => {
|
|
13116
13164
|
const interfaces = os.networkInterfaces();
|
|
@@ -13219,13 +13267,13 @@ function buildIosNativeRunCommand({
|
|
|
13219
13267
|
scheme = "App",
|
|
13220
13268
|
configuration = "Debug"
|
|
13221
13269
|
}) {
|
|
13222
|
-
const derivedDataPath =
|
|
13270
|
+
const derivedDataPath = path41.join(appRoot, "ios/DerivedData", device.id);
|
|
13223
13271
|
const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
|
|
13224
13272
|
const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
|
|
13225
13273
|
return {
|
|
13226
13274
|
configuration,
|
|
13227
13275
|
derivedDataPath,
|
|
13228
|
-
appPath:
|
|
13276
|
+
appPath: path41.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
|
|
13229
13277
|
xcodebuildArgs: [
|
|
13230
13278
|
"-project",
|
|
13231
13279
|
"App.xcodeproj",
|
|
@@ -13409,11 +13457,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13409
13457
|
const {
|
|
13410
13458
|
name,
|
|
13411
13459
|
basePath: _basePath,
|
|
13460
|
+
indexPath: _indexPath,
|
|
13412
13461
|
version: _version,
|
|
13413
13462
|
buildNum: _buildNum,
|
|
13414
13463
|
assets: _assets,
|
|
13415
13464
|
permissions: _permissions,
|
|
13416
|
-
|
|
13465
|
+
deepLinks: _deepLinks,
|
|
13417
13466
|
files: _files,
|
|
13418
13467
|
appId,
|
|
13419
13468
|
appName,
|
|
@@ -13435,7 +13484,7 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13435
13484
|
...capacitorConfig,
|
|
13436
13485
|
appId,
|
|
13437
13486
|
appName,
|
|
13438
|
-
webDir:
|
|
13487
|
+
webDir: path41.posix.join(".akan", "mobile", name, "www"),
|
|
13439
13488
|
plugins: {
|
|
13440
13489
|
CapacitorCookies: { enabled: true },
|
|
13441
13490
|
...pluginsConfig,
|
|
@@ -13492,10 +13541,10 @@ class CapacitorApp {
|
|
|
13492
13541
|
constructor(app, target) {
|
|
13493
13542
|
this.app = app;
|
|
13494
13543
|
this.target = target;
|
|
13495
|
-
this.targetRootPath =
|
|
13496
|
-
this.targetRoot =
|
|
13497
|
-
this.targetWebRoot =
|
|
13498
|
-
this.targetAssetRoot =
|
|
13544
|
+
this.targetRootPath = path41.posix.join(".akan", "mobile", this.target.name);
|
|
13545
|
+
this.targetRoot = path41.join(this.app.cwdPath, this.targetRootPath);
|
|
13546
|
+
this.targetWebRoot = path41.join(this.targetRoot, "www");
|
|
13547
|
+
this.targetAssetRoot = path41.join(this.targetRoot, "assets");
|
|
13499
13548
|
this.project = new MobileProject(this.app.cwdPath, {
|
|
13500
13549
|
android: { path: this.androidRootPath },
|
|
13501
13550
|
ios: { path: this.iosProjectPath }
|
|
@@ -13510,9 +13559,9 @@ class CapacitorApp {
|
|
|
13510
13559
|
await mkdir11(this.targetRoot, { recursive: true });
|
|
13511
13560
|
if (regenerate) {
|
|
13512
13561
|
if (!platform || platform === "ios")
|
|
13513
|
-
await rm5(
|
|
13562
|
+
await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
|
|
13514
13563
|
if (!platform || platform === "android")
|
|
13515
|
-
await rm5(
|
|
13564
|
+
await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
|
|
13516
13565
|
}
|
|
13517
13566
|
const project = this.project;
|
|
13518
13567
|
await this.project.load();
|
|
@@ -13535,7 +13584,7 @@ class CapacitorApp {
|
|
|
13535
13584
|
await this.#prepareExternalFiles("ios");
|
|
13536
13585
|
await this.#applyIosMetadata();
|
|
13537
13586
|
await this.#applyPermissions();
|
|
13538
|
-
await this.#
|
|
13587
|
+
await this.#applyDeepLinks("ios", { operation, env });
|
|
13539
13588
|
await this.project.commit();
|
|
13540
13589
|
await this.#generateAssets({ operation, env });
|
|
13541
13590
|
this.app.verbose(`syncing iOS`);
|
|
@@ -13635,7 +13684,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13635
13684
|
const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
|
|
13636
13685
|
try {
|
|
13637
13686
|
await this.#spawn("xcodebuild", xcodebuildArgs, {
|
|
13638
|
-
cwd:
|
|
13687
|
+
cwd: path41.join(this.app.cwdPath, this.iosProjectPath),
|
|
13639
13688
|
env: mobileEnv
|
|
13640
13689
|
});
|
|
13641
13690
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
@@ -13660,7 +13709,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13660
13709
|
return isRecord2(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
13661
13710
|
}
|
|
13662
13711
|
async#getIosDevelopmentTeam() {
|
|
13663
|
-
const pbxprojPath =
|
|
13712
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
13664
13713
|
if (!await Bun.file(pbxprojPath).exists())
|
|
13665
13714
|
return;
|
|
13666
13715
|
return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
|
|
@@ -13678,15 +13727,16 @@ ${error.message}`;
|
|
|
13678
13727
|
await this.#prepareExternalFiles("android");
|
|
13679
13728
|
await this.#applyAndroidMetadata();
|
|
13680
13729
|
await this.#applyPermissions();
|
|
13681
|
-
await this.#
|
|
13730
|
+
await this.#applyDeepLinks("android", { operation, env });
|
|
13682
13731
|
await this.project.commit();
|
|
13683
13732
|
await this.#generateAssets({ operation, env });
|
|
13684
13733
|
await this.#ensureAndroidAssetsDir();
|
|
13685
13734
|
await this.#ensureAndroidDebugKeystore();
|
|
13686
13735
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13736
|
+
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
13687
13737
|
}
|
|
13688
13738
|
async#updateAndroidBuildTypes() {
|
|
13689
|
-
const appGradle = await FileEditor.create(
|
|
13739
|
+
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
13690
13740
|
const buildTypesBlock = `
|
|
13691
13741
|
debug {
|
|
13692
13742
|
applicationIdSuffix ".debug"
|
|
@@ -13730,7 +13780,7 @@ ${error.message}`;
|
|
|
13730
13780
|
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
13731
13781
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
13732
13782
|
stdio: "inherit",
|
|
13733
|
-
cwd:
|
|
13783
|
+
cwd: path41.join(this.app.cwdPath, this.androidRootPath),
|
|
13734
13784
|
env: await this.#commandEnv("release", env)
|
|
13735
13785
|
});
|
|
13736
13786
|
}
|
|
@@ -13738,10 +13788,10 @@ ${error.message}`;
|
|
|
13738
13788
|
await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
|
|
13739
13789
|
}
|
|
13740
13790
|
async#ensureAndroidAssetsDir() {
|
|
13741
|
-
await mkdir11(
|
|
13791
|
+
await mkdir11(path41.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
13742
13792
|
}
|
|
13743
13793
|
async#ensureAndroidDebugKeystore() {
|
|
13744
|
-
const keystorePath =
|
|
13794
|
+
const keystorePath = path41.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
13745
13795
|
if (await Bun.file(keystorePath).exists())
|
|
13746
13796
|
return;
|
|
13747
13797
|
await this.#spawn("keytool", [
|
|
@@ -13780,7 +13830,7 @@ ${error.message}`;
|
|
|
13780
13830
|
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
13781
13831
|
}
|
|
13782
13832
|
async#assertAndroidReleaseSigningConfig() {
|
|
13783
|
-
const gradlePropertiesPath =
|
|
13833
|
+
const gradlePropertiesPath = path41.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
|
|
13784
13834
|
const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
|
|
13785
13835
|
const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
|
|
13786
13836
|
if (missingKeys.length > 0)
|
|
@@ -13807,16 +13857,20 @@ ${error.message}`;
|
|
|
13807
13857
|
await this.#prepareAndroid({ operation: "release", env: "main" });
|
|
13808
13858
|
}
|
|
13809
13859
|
async prepareWww() {
|
|
13810
|
-
const htmlSource =
|
|
13860
|
+
const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
|
|
13811
13861
|
if (!await Bun.file(htmlSource).exists())
|
|
13812
13862
|
throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
|
|
13813
13863
|
await rm5(this.targetWebRoot, { recursive: true, force: true });
|
|
13814
13864
|
await mkdir11(this.targetWebRoot, { recursive: true });
|
|
13815
|
-
await Bun.write(
|
|
13865
|
+
await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
|
|
13816
13866
|
}
|
|
13817
13867
|
#injectMobileTargetMeta(html) {
|
|
13818
13868
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
13819
|
-
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13869
|
+
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13870
|
+
name: this.target.name,
|
|
13871
|
+
basePath: basePath2,
|
|
13872
|
+
indexPath: this.target.indexPath
|
|
13873
|
+
})};</script>`;
|
|
13820
13874
|
if (html.includes("window.__AKAN_MOBILE_TARGET__"))
|
|
13821
13875
|
return html;
|
|
13822
13876
|
return html.replace(/<\/head\s*>/i, `${script}
|
|
@@ -13832,7 +13886,7 @@ ${error.message}`;
|
|
|
13832
13886
|
});
|
|
13833
13887
|
const content = `${JSON.stringify(config, null, 2)}
|
|
13834
13888
|
`;
|
|
13835
|
-
await Bun.write(
|
|
13889
|
+
await Bun.write(path41.join(this.targetRoot, "capacitor.config.json"), content);
|
|
13836
13890
|
return content;
|
|
13837
13891
|
}
|
|
13838
13892
|
async#prepareTargetAssets() {
|
|
@@ -13840,11 +13894,11 @@ ${error.message}`;
|
|
|
13840
13894
|
return;
|
|
13841
13895
|
await mkdir11(this.targetAssetRoot, { recursive: true });
|
|
13842
13896
|
if (this.target.assets.icon)
|
|
13843
|
-
await cp2(
|
|
13897
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.icon), path41.join(this.targetAssetRoot, "icon.png"), {
|
|
13844
13898
|
force: true
|
|
13845
13899
|
});
|
|
13846
13900
|
if (this.target.assets.splash)
|
|
13847
|
-
await cp2(
|
|
13901
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.splash), path41.join(this.targetAssetRoot, "splash.png"), {
|
|
13848
13902
|
force: true
|
|
13849
13903
|
});
|
|
13850
13904
|
}
|
|
@@ -13852,11 +13906,11 @@ ${error.message}`;
|
|
|
13852
13906
|
const files = this.target.files?.[platform];
|
|
13853
13907
|
if (!files)
|
|
13854
13908
|
return;
|
|
13855
|
-
const platformRoot =
|
|
13909
|
+
const platformRoot = path41.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
|
|
13856
13910
|
await Promise.all(Object.entries(files).map(async ([to, from]) => {
|
|
13857
|
-
const targetPath =
|
|
13858
|
-
await mkdir11(
|
|
13859
|
-
await cp2(
|
|
13911
|
+
const targetPath = path41.join(platformRoot, to);
|
|
13912
|
+
await mkdir11(path41.dirname(targetPath), { recursive: true });
|
|
13913
|
+
await cp2(path41.join(this.app.cwdPath, from), targetPath, { force: true });
|
|
13860
13914
|
}));
|
|
13861
13915
|
}
|
|
13862
13916
|
async#generateAssets({ operation, env }) {
|
|
@@ -13866,7 +13920,7 @@ ${error.message}`;
|
|
|
13866
13920
|
"@capacitor/assets",
|
|
13867
13921
|
"generate",
|
|
13868
13922
|
"--assetPath",
|
|
13869
|
-
|
|
13923
|
+
path41.posix.join(this.targetRootPath, "assets"),
|
|
13870
13924
|
"--iosProject",
|
|
13871
13925
|
this.iosProjectPath,
|
|
13872
13926
|
"--androidProject",
|
|
@@ -13899,25 +13953,48 @@ ${error.message}`;
|
|
|
13899
13953
|
await this.addPush();
|
|
13900
13954
|
}
|
|
13901
13955
|
}
|
|
13902
|
-
async#
|
|
13903
|
-
const
|
|
13904
|
-
if (!
|
|
13956
|
+
async#applyDeepLinks(platform, { operation, env }) {
|
|
13957
|
+
const deepLinks = this.target.deepLinks;
|
|
13958
|
+
if (!deepLinks)
|
|
13905
13959
|
return;
|
|
13906
|
-
const schemes =
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13960
|
+
const schemes = deepLinks.schemes ?? [];
|
|
13961
|
+
const domains = deepLinks.domains ?? [];
|
|
13962
|
+
if (domains.length > 0)
|
|
13963
|
+
this.#assertDeepLinkVerificationConfig(platform, { operation, env });
|
|
13964
|
+
if (platform === "ios") {
|
|
13965
|
+
if (schemes.length > 0) {
|
|
13966
|
+
await this.#setPermissionInIos({
|
|
13967
|
+
appTransportSecurity: ""
|
|
13968
|
+
});
|
|
13969
|
+
await this.#setUrlSchemesInIos(schemes);
|
|
13970
|
+
}
|
|
13971
|
+
if (domains.length > 0)
|
|
13972
|
+
await this.#setAssociatedDomainsInIos(domains);
|
|
13973
|
+
return;
|
|
13974
|
+
}
|
|
13975
|
+
if (platform === "android") {
|
|
13911
13976
|
for (const scheme of schemes) {
|
|
13912
13977
|
this.project.android.getAndroidManifest().injectFragment("activity", `<intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="${scheme}" /></intent-filter>`);
|
|
13913
13978
|
}
|
|
13979
|
+
for (const domain of domains) {
|
|
13980
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
13981
|
+
this.project.android.getAndroidManifest().injectFragment("activity", `<intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" /></intent-filter>`);
|
|
13982
|
+
}
|
|
13983
|
+
await this.#setDeepLinksInAndroid(schemes, domains);
|
|
13914
13984
|
}
|
|
13915
|
-
|
|
13916
|
-
|
|
13985
|
+
}
|
|
13986
|
+
#assertDeepLinkVerificationConfig(platform, { operation, env }) {
|
|
13987
|
+
const deepLinks = this.target.deepLinks;
|
|
13988
|
+
if (!deepLinks?.domains?.length)
|
|
13989
|
+
return;
|
|
13990
|
+
if (platform === "ios" && !deepLinks.ios?.teamId) {
|
|
13991
|
+
throw new Error(`Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.ios.teamId in apps/${this.app.name}/akan.config.ts`);
|
|
13917
13992
|
}
|
|
13918
|
-
|
|
13919
|
-
const
|
|
13920
|
-
|
|
13993
|
+
if (platform === "android" && !deepLinks.android?.sha256CertFingerprints?.length) {
|
|
13994
|
+
const message = `Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.android.sha256CertFingerprints in apps/${this.app.name}/akan.config.ts`;
|
|
13995
|
+
if (operation === "release" || env === "main")
|
|
13996
|
+
throw new Error(message);
|
|
13997
|
+
this.app.logger.warn(message);
|
|
13921
13998
|
}
|
|
13922
13999
|
}
|
|
13923
14000
|
async#commandEnv(operation, env) {
|
|
@@ -13938,6 +14015,8 @@ ${error.message}`;
|
|
|
13938
14015
|
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
13939
14016
|
if (basePath2)
|
|
13940
14017
|
params.set("akanMobileBasePath", basePath2);
|
|
14018
|
+
if (this.target.indexPath)
|
|
14019
|
+
params.set("akanMobileIndexPath", this.target.indexPath);
|
|
13941
14020
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
13942
14021
|
}
|
|
13943
14022
|
async#clearRootCapacitorConfigs() {
|
|
@@ -13997,6 +14076,112 @@ ${error.message}`;
|
|
|
13997
14076
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
|
|
13998
14077
|
]);
|
|
13999
14078
|
}
|
|
14079
|
+
async#setUrlSchemesInIos(schemes) {
|
|
14080
|
+
const urlTypes = schemes.map((scheme) => ({
|
|
14081
|
+
CFBundleURLName: this.target.appId,
|
|
14082
|
+
CFBundleURLSchemes: [scheme]
|
|
14083
|
+
}));
|
|
14084
|
+
await Promise.all([
|
|
14085
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
14086
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
|
|
14087
|
+
]);
|
|
14088
|
+
}
|
|
14089
|
+
async#setAssociatedDomainsInIos(domains) {
|
|
14090
|
+
const entitlementsRelPath = "App/App.entitlements";
|
|
14091
|
+
const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
14092
|
+
const values = domains.map((domain) => `applinks:${domain}`);
|
|
14093
|
+
const body = [
|
|
14094
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
14095
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14096
|
+
'<plist version="1.0">',
|
|
14097
|
+
"<dict>",
|
|
14098
|
+
" <key>com.apple.developer.associated-domains</key>",
|
|
14099
|
+
" <array>",
|
|
14100
|
+
...values.map((value) => ` <string>${value}</string>`),
|
|
14101
|
+
" </array>",
|
|
14102
|
+
"</dict>",
|
|
14103
|
+
"</plist>",
|
|
14104
|
+
""
|
|
14105
|
+
].join(`
|
|
14106
|
+
`);
|
|
14107
|
+
await writeFile2(entitlementsPath, body);
|
|
14108
|
+
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
14109
|
+
}
|
|
14110
|
+
async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
|
|
14111
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
14112
|
+
const lines = (await readFile2(pbxprojPath, "utf8")).split(`
|
|
14113
|
+
`);
|
|
14114
|
+
let changed = false;
|
|
14115
|
+
for (let index = 0;index < lines.length; index++) {
|
|
14116
|
+
const line = lines[index] ?? "";
|
|
14117
|
+
if (!line.includes(`PRODUCT_BUNDLE_IDENTIFIER = ${this.target.appId};`))
|
|
14118
|
+
continue;
|
|
14119
|
+
let start = index;
|
|
14120
|
+
while (start >= 0 && !lines[start]?.includes("buildSettings = {"))
|
|
14121
|
+
start--;
|
|
14122
|
+
let end = index;
|
|
14123
|
+
while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? ""))
|
|
14124
|
+
end++;
|
|
14125
|
+
const settings = lines.slice(start, end + 1);
|
|
14126
|
+
if (settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS")))
|
|
14127
|
+
continue;
|
|
14128
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
14129
|
+
lines.splice(index, 0, `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
|
|
14130
|
+
index++;
|
|
14131
|
+
changed = true;
|
|
14132
|
+
}
|
|
14133
|
+
if (changed)
|
|
14134
|
+
await writeFile2(pbxprojPath, lines.join(`
|
|
14135
|
+
`));
|
|
14136
|
+
}
|
|
14137
|
+
async#setUrlSchemesInAndroid(schemes) {
|
|
14138
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14139
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14140
|
+
let changed = false;
|
|
14141
|
+
for (const scheme of schemes) {
|
|
14142
|
+
if (manifest.includes(`android:scheme="${scheme}"`))
|
|
14143
|
+
continue;
|
|
14144
|
+
const filter = [
|
|
14145
|
+
" <intent-filter>",
|
|
14146
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14147
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14148
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14149
|
+
` <data android:scheme="${scheme}" />`,
|
|
14150
|
+
" </intent-filter>"
|
|
14151
|
+
].join(`
|
|
14152
|
+
`);
|
|
14153
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14154
|
+
${filter}$1`);
|
|
14155
|
+
changed = true;
|
|
14156
|
+
}
|
|
14157
|
+
if (changed)
|
|
14158
|
+
await writeFile2(manifestPath, manifest);
|
|
14159
|
+
}
|
|
14160
|
+
async#setDeepLinksInAndroid(schemes, domains) {
|
|
14161
|
+
await this.#setUrlSchemesInAndroid(schemes);
|
|
14162
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14163
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14164
|
+
let changed = false;
|
|
14165
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
14166
|
+
for (const domain of domains) {
|
|
14167
|
+
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"'))
|
|
14168
|
+
continue;
|
|
14169
|
+
const filter = [
|
|
14170
|
+
' <intent-filter android:autoVerify="true">',
|
|
14171
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14172
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14173
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14174
|
+
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
14175
|
+
" </intent-filter>"
|
|
14176
|
+
].join(`
|
|
14177
|
+
`);
|
|
14178
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14179
|
+
${filter}$1`);
|
|
14180
|
+
changed = true;
|
|
14181
|
+
}
|
|
14182
|
+
if (changed)
|
|
14183
|
+
await writeFile2(manifestPath, manifest);
|
|
14184
|
+
}
|
|
14000
14185
|
#setFeaturesInAndroid(features) {
|
|
14001
14186
|
for (const feature of features) {
|
|
14002
14187
|
if (this.#hasFeatureInAndroid(feature)) {
|
|
@@ -14076,7 +14261,7 @@ var Pkg = createInternalArgToken("Pkg");
|
|
|
14076
14261
|
var Module = createInternalArgToken("Module");
|
|
14077
14262
|
var Workspace = createInternalArgToken("Workspace");
|
|
14078
14263
|
// pkgs/@akanjs/devkit/commandDecorators/command.ts
|
|
14079
|
-
import
|
|
14264
|
+
import path42 from "path";
|
|
14080
14265
|
import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
|
|
14081
14266
|
import { Logger as Logger10 } from "akanjs/common";
|
|
14082
14267
|
import chalk6 from "chalk";
|
|
@@ -14580,7 +14765,7 @@ var runCommands = async (...commands) => {
|
|
|
14580
14765
|
process.exit(1);
|
|
14581
14766
|
});
|
|
14582
14767
|
const __dirname2 = getDirname(import.meta.url);
|
|
14583
|
-
const packageJsonCandidates = [`${
|
|
14768
|
+
const packageJsonCandidates = [`${path42.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
|
|
14584
14769
|
let cliPackageJson = null;
|
|
14585
14770
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
14586
14771
|
if (!await FileSys.fileExists(packageJsonPath))
|
|
@@ -14860,8 +15045,8 @@ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
|
|
|
14860
15045
|
return importSpecifiers;
|
|
14861
15046
|
};
|
|
14862
15047
|
var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
|
|
14863
|
-
const configFile = ts10.readConfigFile(tsConfigPath, (
|
|
14864
|
-
return ts10.sys.readFile(
|
|
15048
|
+
const configFile = ts10.readConfigFile(tsConfigPath, (path43) => {
|
|
15049
|
+
return ts10.sys.readFile(path43);
|
|
14865
15050
|
});
|
|
14866
15051
|
return ts10.parseJsonConfigFileContent(configFile.config, ts10.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
|
|
14867
15052
|
};
|
|
@@ -15077,6 +15262,571 @@ ${document}
|
|
|
15077
15262
|
`;
|
|
15078
15263
|
}
|
|
15079
15264
|
}
|
|
15265
|
+
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15266
|
+
import { createHash } from "crypto";
|
|
15267
|
+
import { readdir as readdir3, readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
15268
|
+
import path43 from "path";
|
|
15269
|
+
import ignore2 from "ignore";
|
|
15270
|
+
import ts11 from "typescript";
|
|
15271
|
+
var MAX_FILE_LINES = 2000;
|
|
15272
|
+
var PLACEHOLDER_EXPORT_NAMES = new Set([
|
|
15273
|
+
"aa",
|
|
15274
|
+
"dumb",
|
|
15275
|
+
"dumb2",
|
|
15276
|
+
"someBaseLogic",
|
|
15277
|
+
"someCommonLogic",
|
|
15278
|
+
"someFrontendLogic",
|
|
15279
|
+
"someBackendLogic"
|
|
15280
|
+
]);
|
|
15281
|
+
var SUGGESTED_RULES = [
|
|
15282
|
+
"Keep generated scanSync index files out of hand-written changes; generated indexes should only contain one-depth export statements.",
|
|
15283
|
+
"Generated Akan index files should not contain placeholder exports such as aa, dumb, dumb2, or some*Logic.",
|
|
15284
|
+
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15285
|
+
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15286
|
+
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15287
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15288
|
+
"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.",
|
|
15289
|
+
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15290
|
+
"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.",
|
|
15291
|
+
"Move shared app utilities to apps/*/common instead of creating apps/*/base.",
|
|
15292
|
+
"Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
|
|
15293
|
+
];
|
|
15294
|
+
var APP_ROOT_FILES = new Set([
|
|
15295
|
+
"akan.app.json",
|
|
15296
|
+
"akan.config.ts",
|
|
15297
|
+
"capacitor.config.ts",
|
|
15298
|
+
"client.ts",
|
|
15299
|
+
"main.ts",
|
|
15300
|
+
"package.json",
|
|
15301
|
+
"server.ts",
|
|
15302
|
+
"tsconfig.json"
|
|
15303
|
+
]);
|
|
15304
|
+
var LIB_ROOT_FILES = new Set([
|
|
15305
|
+
"cnst.ts",
|
|
15306
|
+
"db.ts",
|
|
15307
|
+
"dict.ts",
|
|
15308
|
+
"option.ts",
|
|
15309
|
+
"sig.ts",
|
|
15310
|
+
"srv.ts",
|
|
15311
|
+
"st.ts",
|
|
15312
|
+
"useClient.ts",
|
|
15313
|
+
"useServer.ts"
|
|
15314
|
+
]);
|
|
15315
|
+
var CONVENTION_SUFFIXES = [
|
|
15316
|
+
".constant.ts",
|
|
15317
|
+
".dictionary.ts",
|
|
15318
|
+
".document.ts",
|
|
15319
|
+
".service.ts",
|
|
15320
|
+
".signal.ts",
|
|
15321
|
+
".store.ts"
|
|
15322
|
+
];
|
|
15323
|
+
|
|
15324
|
+
class AkanQualityScanner {
|
|
15325
|
+
async scan(workspaceRoot) {
|
|
15326
|
+
const targetFiles = await this.#collectTargetFiles(workspaceRoot);
|
|
15327
|
+
const sourceFiles = await Promise.all(targetFiles.map((file) => this.#readSourceFile(workspaceRoot, file)));
|
|
15328
|
+
const warnings = [
|
|
15329
|
+
...this.#scanGlobalQuality(sourceFiles),
|
|
15330
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanSingleFileQuality(sourceFile2)),
|
|
15331
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanConventionQuality(sourceFile2)),
|
|
15332
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanLayoutQuality(sourceFile2))
|
|
15333
|
+
];
|
|
15334
|
+
return {
|
|
15335
|
+
workspaceRoot,
|
|
15336
|
+
scannedFiles: sourceFiles.length,
|
|
15337
|
+
warnings: warnings.sort(compareWarnings),
|
|
15338
|
+
suggestedRules: SUGGESTED_RULES
|
|
15339
|
+
};
|
|
15340
|
+
}
|
|
15341
|
+
async#collectTargetFiles(workspaceRoot) {
|
|
15342
|
+
const ignoreFilter = ignore2().add(await this.#readGitIgnore(workspaceRoot));
|
|
15343
|
+
const files = [];
|
|
15344
|
+
for (const targetRoot of ["apps", "libs"]) {
|
|
15345
|
+
const absoluteTargetRoot = path43.join(workspaceRoot, targetRoot);
|
|
15346
|
+
if (!await isDirectory(absoluteTargetRoot))
|
|
15347
|
+
continue;
|
|
15348
|
+
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
15349
|
+
}
|
|
15350
|
+
return files.sort();
|
|
15351
|
+
}
|
|
15352
|
+
async#readGitIgnore(workspaceRoot) {
|
|
15353
|
+
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15354
|
+
if (!await Bun.file(gitIgnorePath).exists())
|
|
15355
|
+
return [];
|
|
15356
|
+
return (await readFile3(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15357
|
+
}
|
|
15358
|
+
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15359
|
+
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
15360
|
+
for (const entry of entries) {
|
|
15361
|
+
const absolutePath = path43.join(currentPath, entry.name);
|
|
15362
|
+
const relativePath = toPosix(path43.relative(workspaceRoot, absolutePath));
|
|
15363
|
+
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
15364
|
+
continue;
|
|
15365
|
+
if (entry.isDirectory()) {
|
|
15366
|
+
await this.#walkTargetFiles(workspaceRoot, absolutePath, ignoreFilter, files);
|
|
15367
|
+
continue;
|
|
15368
|
+
}
|
|
15369
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
15370
|
+
files.push(relativePath);
|
|
15371
|
+
}
|
|
15372
|
+
}
|
|
15373
|
+
}
|
|
15374
|
+
async#readSourceFile(workspaceRoot, file) {
|
|
15375
|
+
const absolutePath = path43.join(workspaceRoot, file);
|
|
15376
|
+
const content = await readFile3(absolutePath, "utf8");
|
|
15377
|
+
return {
|
|
15378
|
+
file,
|
|
15379
|
+
absolutePath,
|
|
15380
|
+
content,
|
|
15381
|
+
sourceFile: ts11.createSourceFile(file, content, ts11.ScriptTarget.Latest, true, getScriptKind(file))
|
|
15382
|
+
};
|
|
15383
|
+
}
|
|
15384
|
+
#scanGlobalQuality(sourceFiles) {
|
|
15385
|
+
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile2) => getExportedFunctionLikes(sourceFile2));
|
|
15386
|
+
const warnings = [];
|
|
15387
|
+
for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
|
|
15388
|
+
if (declarations.length < 2)
|
|
15389
|
+
continue;
|
|
15390
|
+
warnings.push({
|
|
15391
|
+
rule: "akan.global.duplicate-exported-function-name",
|
|
15392
|
+
scope: "global",
|
|
15393
|
+
severity: "warning",
|
|
15394
|
+
message: `Exported function or class name "${name}" is declared in ${declarations.length} files.`,
|
|
15395
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15396
|
+
});
|
|
15397
|
+
}
|
|
15398
|
+
const declarationsWithBody = exportedFunctionLikes.filter((declaration) => declaration.bodyFingerprint);
|
|
15399
|
+
for (const [fingerprint, declarations] of groupBy(declarationsWithBody, (declaration) => declaration.bodyFingerprint ?? "")) {
|
|
15400
|
+
const uniqueNames = new Set(declarations.map((declaration) => declaration.name));
|
|
15401
|
+
if (declarations.length < 2 || uniqueNames.size < 2 || fingerprint === "")
|
|
15402
|
+
continue;
|
|
15403
|
+
warnings.push({
|
|
15404
|
+
rule: "akan.global.duplicate-exported-function-body",
|
|
15405
|
+
scope: "global",
|
|
15406
|
+
severity: "warning",
|
|
15407
|
+
message: `Exported functions/classes share the same implementation body: ${declarations.map((declaration) => declaration.name).join(", ")}.`,
|
|
15408
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15409
|
+
});
|
|
15410
|
+
}
|
|
15411
|
+
return warnings;
|
|
15412
|
+
}
|
|
15413
|
+
#scanSingleFileQuality(sourceFile2) {
|
|
15414
|
+
const warnings = [];
|
|
15415
|
+
const lineCount = sourceFile2.content.split(/\r?\n/).length;
|
|
15416
|
+
const recommendedLineLimit = getRecommendedLineLimit(sourceFile2.file);
|
|
15417
|
+
if (recommendedLineLimit && lineCount > recommendedLineLimit) {
|
|
15418
|
+
warnings.push({
|
|
15419
|
+
rule: "akan.file.recommended-max-lines",
|
|
15420
|
+
scope: "file",
|
|
15421
|
+
severity: "warning",
|
|
15422
|
+
file: sourceFile2.file,
|
|
15423
|
+
message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
|
|
15424
|
+
});
|
|
15425
|
+
}
|
|
15426
|
+
if (lineCount > MAX_FILE_LINES) {
|
|
15427
|
+
warnings.push({
|
|
15428
|
+
rule: "akan.file.max-lines",
|
|
15429
|
+
scope: "file",
|
|
15430
|
+
severity: "warning",
|
|
15431
|
+
file: sourceFile2.file,
|
|
15432
|
+
message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
|
|
15433
|
+
});
|
|
15434
|
+
}
|
|
15435
|
+
warnings.push(...getPlaceholderExportWarnings(sourceFile2));
|
|
15436
|
+
warnings.push(...getDictionaryTextWarnings(sourceFile2));
|
|
15437
|
+
warnings.push(...getGlobalMutationWarnings(sourceFile2));
|
|
15438
|
+
const exportedClassNames = getExportedClassNames(sourceFile2.sourceFile);
|
|
15439
|
+
if (exportedClassNames.length === 0)
|
|
15440
|
+
return warnings;
|
|
15441
|
+
const allowedInterfaceNames = new Set(exportedClassNames.map((name) => `${name}Options`));
|
|
15442
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15443
|
+
if (declaration.kind === "class" && exportedClassNames.includes(declaration.name))
|
|
15444
|
+
continue;
|
|
15445
|
+
if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
|
|
15446
|
+
continue;
|
|
15447
|
+
warnings.push({
|
|
15448
|
+
rule: "akan.file.class-export-global-declaration",
|
|
15449
|
+
scope: "file",
|
|
15450
|
+
severity: "warning",
|
|
15451
|
+
file: sourceFile2.file,
|
|
15452
|
+
line: declaration.line,
|
|
15453
|
+
message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
|
|
15454
|
+
});
|
|
15455
|
+
}
|
|
15456
|
+
return warnings;
|
|
15457
|
+
}
|
|
15458
|
+
#scanConventionQuality(sourceFile2) {
|
|
15459
|
+
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile2.file.endsWith(candidate));
|
|
15460
|
+
if (!suffix)
|
|
15461
|
+
return [];
|
|
15462
|
+
const modelName = toPascalCase(path43.basename(sourceFile2.file, suffix));
|
|
15463
|
+
const warnings = [];
|
|
15464
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15465
|
+
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
15466
|
+
continue;
|
|
15467
|
+
warnings.push({
|
|
15468
|
+
rule: `akan.convention${suffix.replace(".ts", "")}`,
|
|
15469
|
+
scope: "convention",
|
|
15470
|
+
severity: "warning",
|
|
15471
|
+
file: sourceFile2.file,
|
|
15472
|
+
line: declaration.line,
|
|
15473
|
+
message: `${path43.basename(sourceFile2.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
15474
|
+
});
|
|
15475
|
+
}
|
|
15476
|
+
return warnings;
|
|
15477
|
+
}
|
|
15478
|
+
#scanLayoutQuality(sourceFile2) {
|
|
15479
|
+
const segments = sourceFile2.file.split("/");
|
|
15480
|
+
const warnings = [];
|
|
15481
|
+
if (segments[0] === "apps" && segments.length === 3 && !APP_ROOT_FILES.has(segments[2])) {
|
|
15482
|
+
warnings.push({
|
|
15483
|
+
rule: "akan.layout.app-root-file",
|
|
15484
|
+
scope: "layout",
|
|
15485
|
+
severity: "warning",
|
|
15486
|
+
file: sourceFile2.file,
|
|
15487
|
+
message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
|
|
15488
|
+
});
|
|
15489
|
+
}
|
|
15490
|
+
const libRootFile = getLibRootFile(sourceFile2.file);
|
|
15491
|
+
if (libRootFile && !LIB_ROOT_FILES.has(libRootFile)) {
|
|
15492
|
+
warnings.push({
|
|
15493
|
+
rule: "akan.layout.lib-root-file",
|
|
15494
|
+
scope: "layout",
|
|
15495
|
+
severity: "warning",
|
|
15496
|
+
file: sourceFile2.file,
|
|
15497
|
+
message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
|
|
15498
|
+
});
|
|
15499
|
+
}
|
|
15500
|
+
const moduleUiWarning = getModuleUiWarning(sourceFile2.file);
|
|
15501
|
+
if (moduleUiWarning)
|
|
15502
|
+
warnings.push(moduleUiWarning);
|
|
15503
|
+
return warnings;
|
|
15504
|
+
}
|
|
15505
|
+
}
|
|
15506
|
+
function formatQualityScanResult(result) {
|
|
15507
|
+
const sections = [
|
|
15508
|
+
"Akan Code Quality Scan",
|
|
15509
|
+
`workspace: ${result.workspaceRoot}`,
|
|
15510
|
+
`scanned files: ${result.scannedFiles}`,
|
|
15511
|
+
`warnings: ${result.warnings.length}`,
|
|
15512
|
+
"",
|
|
15513
|
+
"Warnings:",
|
|
15514
|
+
"",
|
|
15515
|
+
...formatQualityWarnings(result.warnings),
|
|
15516
|
+
"",
|
|
15517
|
+
"Suggested quality rules:",
|
|
15518
|
+
"",
|
|
15519
|
+
...result.suggestedRules.map((rule) => ` - ${rule}`)
|
|
15520
|
+
];
|
|
15521
|
+
return sections.join(`
|
|
15522
|
+
`);
|
|
15523
|
+
}
|
|
15524
|
+
function formatQualityWarnings(warnings) {
|
|
15525
|
+
if (warnings.length === 0)
|
|
15526
|
+
return ["No warnings found."];
|
|
15527
|
+
return warnings.flatMap((warning) => {
|
|
15528
|
+
const location = formatQualityLocation(warning.file, warning.line);
|
|
15529
|
+
const lines = [`${location} - warning ${warning.rule}: ${warning.message}`];
|
|
15530
|
+
if (warning.locations?.length) {
|
|
15531
|
+
lines.push(...warning.locations.map(({ file, line }) => ` note: related location ${formatQualityLocation(file, line)}`));
|
|
15532
|
+
}
|
|
15533
|
+
return lines;
|
|
15534
|
+
});
|
|
15535
|
+
}
|
|
15536
|
+
function formatQualityLocation(file, line) {
|
|
15537
|
+
return `${file ?? "<global>"}:${line ?? 1}:1`;
|
|
15538
|
+
}
|
|
15539
|
+
function getExportedFunctionLikes(sourceFile2) {
|
|
15540
|
+
const declarations = [];
|
|
15541
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15542
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15543
|
+
declarations.push({
|
|
15544
|
+
name: statement.name.text,
|
|
15545
|
+
kind: "function",
|
|
15546
|
+
file: sourceFile2.file,
|
|
15547
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15548
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement.body)
|
|
15549
|
+
});
|
|
15550
|
+
}
|
|
15551
|
+
if (ts11.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15552
|
+
declarations.push({
|
|
15553
|
+
name: statement.name.text,
|
|
15554
|
+
kind: "class",
|
|
15555
|
+
file: sourceFile2.file,
|
|
15556
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15557
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement)
|
|
15558
|
+
});
|
|
15559
|
+
}
|
|
15560
|
+
if (ts11.isVariableStatement(statement) && isExported(statement)) {
|
|
15561
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
15562
|
+
if (!ts11.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
15563
|
+
continue;
|
|
15564
|
+
declarations.push({
|
|
15565
|
+
name: declaration.name.text,
|
|
15566
|
+
kind: "function-variable",
|
|
15567
|
+
file: sourceFile2.file,
|
|
15568
|
+
line: getLine(sourceFile2.sourceFile, declaration),
|
|
15569
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, declaration.initializer)
|
|
15570
|
+
});
|
|
15571
|
+
}
|
|
15572
|
+
}
|
|
15573
|
+
}
|
|
15574
|
+
return declarations;
|
|
15575
|
+
}
|
|
15576
|
+
function getExportedClassNames(sourceFile2) {
|
|
15577
|
+
return sourceFile2.statements.filter((statement) => ts11.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
|
|
15578
|
+
}
|
|
15579
|
+
function getPlaceholderExportWarnings(sourceFile2) {
|
|
15580
|
+
if (!sourceFile2.file.endsWith("/index.ts") && !sourceFile2.file.endsWith("/index.tsx"))
|
|
15581
|
+
return [];
|
|
15582
|
+
return getTopLevelDeclarations(sourceFile2).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
|
|
15583
|
+
rule: "akan.file.placeholder-export",
|
|
15584
|
+
scope: "file",
|
|
15585
|
+
severity: "warning",
|
|
15586
|
+
file: sourceFile2.file,
|
|
15587
|
+
line: declaration.line,
|
|
15588
|
+
message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
|
|
15589
|
+
}));
|
|
15590
|
+
}
|
|
15591
|
+
function getDictionaryTextWarnings(sourceFile2) {
|
|
15592
|
+
if (!sourceFile2.file.endsWith(".dictionary.ts"))
|
|
15593
|
+
return [];
|
|
15594
|
+
const stalePatterns = [
|
|
15595
|
+
{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
|
|
15596
|
+
{ pattern: /settting/, label: "misspelling: settting" },
|
|
15597
|
+
{ pattern: /\uBC30\uB108 \uC218/, label: "stale copied Korean domain noun: \uBC30\uB108 \uC218" }
|
|
15598
|
+
];
|
|
15599
|
+
return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile2.content, pattern).map((line) => ({
|
|
15600
|
+
rule: "akan.file.dictionary-stale-text",
|
|
15601
|
+
scope: "file",
|
|
15602
|
+
severity: "warning",
|
|
15603
|
+
file: sourceFile2.file,
|
|
15604
|
+
line,
|
|
15605
|
+
message: `Dictionary text appears to contain ${label}.`
|
|
15606
|
+
})));
|
|
15607
|
+
}
|
|
15608
|
+
function getGlobalMutationWarnings(sourceFile2) {
|
|
15609
|
+
const warnings = [];
|
|
15610
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15611
|
+
if (ts11.isModuleDeclaration(statement) && statement.name.getText(sourceFile2.sourceFile) === "global") {
|
|
15612
|
+
warnings.push({
|
|
15613
|
+
rule: "akan.file.global-declaration",
|
|
15614
|
+
scope: "file",
|
|
15615
|
+
severity: "warning",
|
|
15616
|
+
file: sourceFile2.file,
|
|
15617
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15618
|
+
message: "Global declarations require an explicit low-level integration allowlist."
|
|
15619
|
+
});
|
|
15620
|
+
}
|
|
15621
|
+
if (ts11.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
15622
|
+
warnings.push({
|
|
15623
|
+
rule: "akan.file.window-augmentation",
|
|
15624
|
+
scope: "file",
|
|
15625
|
+
severity: "warning",
|
|
15626
|
+
file: sourceFile2.file,
|
|
15627
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15628
|
+
message: "Window augmentation should be isolated to approved browser integration files."
|
|
15629
|
+
});
|
|
15630
|
+
}
|
|
15631
|
+
if (ts11.isExpressionStatement(statement) && statement.expression.getText(sourceFile2.sourceFile).includes(".prototype.")) {
|
|
15632
|
+
warnings.push({
|
|
15633
|
+
rule: "akan.file.prototype-mutation",
|
|
15634
|
+
scope: "file",
|
|
15635
|
+
severity: "warning",
|
|
15636
|
+
file: sourceFile2.file,
|
|
15637
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15638
|
+
message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
|
|
15639
|
+
});
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
return warnings;
|
|
15643
|
+
}
|
|
15644
|
+
function getTopLevelDeclarations(sourceFile2) {
|
|
15645
|
+
return sourceFile2.sourceFile.statements.flatMap((statement) => getTopLevelDeclaration(sourceFile2.sourceFile, statement));
|
|
15646
|
+
}
|
|
15647
|
+
function getTopLevelDeclaration(sourceFile2, statement) {
|
|
15648
|
+
const line = getLine(sourceFile2, statement);
|
|
15649
|
+
if (ts11.isClassDeclaration(statement) && statement.name) {
|
|
15650
|
+
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
15651
|
+
}
|
|
15652
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name) {
|
|
15653
|
+
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
15654
|
+
}
|
|
15655
|
+
if (ts11.isInterfaceDeclaration(statement)) {
|
|
15656
|
+
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
15657
|
+
}
|
|
15658
|
+
if (ts11.isTypeAliasDeclaration(statement)) {
|
|
15659
|
+
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
15660
|
+
}
|
|
15661
|
+
if (ts11.isEnumDeclaration(statement)) {
|
|
15662
|
+
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
15663
|
+
}
|
|
15664
|
+
if (ts11.isVariableStatement(statement)) {
|
|
15665
|
+
return statement.declarationList.declarations.filter((declaration) => ts11.isIdentifier(declaration.name)).map((declaration) => ({
|
|
15666
|
+
name: declaration.name.text,
|
|
15667
|
+
kind: "variable",
|
|
15668
|
+
line: getLine(sourceFile2, declaration),
|
|
15669
|
+
exported: isExported(statement),
|
|
15670
|
+
node: statement
|
|
15671
|
+
}));
|
|
15672
|
+
}
|
|
15673
|
+
if (ts11.isExportDeclaration(statement)) {
|
|
15674
|
+
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
15675
|
+
}
|
|
15676
|
+
return [];
|
|
15677
|
+
}
|
|
15678
|
+
function isAllowedConventionDeclaration(suffix, modelName, declaration) {
|
|
15679
|
+
if (suffix === ".dictionary.ts")
|
|
15680
|
+
return isExportedConst(declaration) && declaration.name === "dictionary";
|
|
15681
|
+
if (suffix === ".constant.ts")
|
|
15682
|
+
return isAllowedConstantDeclaration(modelName, declaration);
|
|
15683
|
+
if (suffix === ".document.ts")
|
|
15684
|
+
return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
|
|
15685
|
+
if (suffix === ".service.ts")
|
|
15686
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Service`;
|
|
15687
|
+
if (suffix === ".signal.ts")
|
|
15688
|
+
return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
|
|
15689
|
+
if (suffix === ".store.ts")
|
|
15690
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Store`;
|
|
15691
|
+
return false;
|
|
15692
|
+
}
|
|
15693
|
+
function isAllowedConstantDeclaration(modelName, declaration) {
|
|
15694
|
+
if (declaration.kind !== "class")
|
|
15695
|
+
return false;
|
|
15696
|
+
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
15697
|
+
return true;
|
|
15698
|
+
if (!ts11.isClassDeclaration(declaration.node))
|
|
15699
|
+
return false;
|
|
15700
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts11.SyntaxKind.ExtendsKeyword);
|
|
15701
|
+
const expression = heritageClause?.types[0]?.expression;
|
|
15702
|
+
return !!expression && expression.getText().startsWith("enumOf(");
|
|
15703
|
+
}
|
|
15704
|
+
function getConventionDescription(suffix, modelName) {
|
|
15705
|
+
if (suffix === ".dictionary.ts")
|
|
15706
|
+
return "export const dictionary";
|
|
15707
|
+
if (suffix === ".constant.ts")
|
|
15708
|
+
return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
|
|
15709
|
+
if (suffix === ".document.ts")
|
|
15710
|
+
return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
|
|
15711
|
+
if (suffix === ".service.ts")
|
|
15712
|
+
return `${modelName}Service`;
|
|
15713
|
+
if (suffix === ".signal.ts")
|
|
15714
|
+
return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
|
|
15715
|
+
return `${modelName}Store`;
|
|
15716
|
+
}
|
|
15717
|
+
function getLibRootFile(file) {
|
|
15718
|
+
const segments = file.split("/");
|
|
15719
|
+
if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
|
|
15720
|
+
return segments[3];
|
|
15721
|
+
if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
|
|
15722
|
+
return segments[4];
|
|
15723
|
+
return null;
|
|
15724
|
+
}
|
|
15725
|
+
function getModuleUiWarning(file) {
|
|
15726
|
+
if (!file.endsWith(".tsx"))
|
|
15727
|
+
return null;
|
|
15728
|
+
const moduleInfo = getModuleInfo(file);
|
|
15729
|
+
if (!moduleInfo)
|
|
15730
|
+
return null;
|
|
15731
|
+
const { moduleName, fileName, kind } = moduleInfo;
|
|
15732
|
+
const pascalName = toPascalCase(moduleName.replace(/^_+/, ""));
|
|
15733
|
+
const allowedSuffixes = kind === "database" ? ["Template", "Unit", "Util", "View", "Zone"] : kind === "service" ? ["Util", "Zone"] : ["Template", "Unit"];
|
|
15734
|
+
const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
|
|
15735
|
+
if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
|
|
15736
|
+
return null;
|
|
15737
|
+
return {
|
|
15738
|
+
rule: "akan.layout.module-ui-file",
|
|
15739
|
+
scope: "layout",
|
|
15740
|
+
severity: "warning",
|
|
15741
|
+
file,
|
|
15742
|
+
message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
|
|
15743
|
+
};
|
|
15744
|
+
}
|
|
15745
|
+
function getModuleInfo(file) {
|
|
15746
|
+
const segments = file.split("/");
|
|
15747
|
+
const libIndex = segments.indexOf("lib");
|
|
15748
|
+
if (libIndex < 0)
|
|
15749
|
+
return null;
|
|
15750
|
+
const moduleName = segments[libIndex + 1];
|
|
15751
|
+
if (moduleName === "__scalar") {
|
|
15752
|
+
if (segments.length !== libIndex + 4)
|
|
15753
|
+
return null;
|
|
15754
|
+
return { moduleName: segments[libIndex + 2], fileName: segments[libIndex + 3], kind: "scalar" };
|
|
15755
|
+
}
|
|
15756
|
+
if (segments.length !== libIndex + 3)
|
|
15757
|
+
return null;
|
|
15758
|
+
const fileName = segments[libIndex + 2];
|
|
15759
|
+
if (moduleName.startsWith("__")) {
|
|
15760
|
+
const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
|
|
15761
|
+
return { moduleName: scalarName, fileName, kind: "scalar" };
|
|
15762
|
+
}
|
|
15763
|
+
if (moduleName.startsWith("_"))
|
|
15764
|
+
return { moduleName, fileName, kind: "service" };
|
|
15765
|
+
return { moduleName, fileName, kind: "database" };
|
|
15766
|
+
}
|
|
15767
|
+
function isExportedConst(declaration) {
|
|
15768
|
+
return declaration.exported && ts11.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts11.NodeFlags.Const) !== 0;
|
|
15769
|
+
}
|
|
15770
|
+
function isExported(node) {
|
|
15771
|
+
return !!ts11.getCombinedModifierFlags(node) && !!(ts11.getCombinedModifierFlags(node) & ts11.ModifierFlags.Export);
|
|
15772
|
+
}
|
|
15773
|
+
function isFunctionLikeInitializer(node) {
|
|
15774
|
+
return !!node && (ts11.isArrowFunction(node) || ts11.isFunctionExpression(node));
|
|
15775
|
+
}
|
|
15776
|
+
function getBodyFingerprint(sourceFile2, node) {
|
|
15777
|
+
if (!node)
|
|
15778
|
+
return;
|
|
15779
|
+
const normalizedBody = node.getText(sourceFile2).replace(/\s+/g, " ").trim();
|
|
15780
|
+
if (normalizedBody.length < 80)
|
|
15781
|
+
return;
|
|
15782
|
+
return createHash("sha256").update(normalizedBody).digest("hex");
|
|
15783
|
+
}
|
|
15784
|
+
function shouldSkipPath(relativePath, isDirectory, ignoreFilter) {
|
|
15785
|
+
const ignorePath = isDirectory ? `${relativePath}/` : relativePath;
|
|
15786
|
+
return relativePath === ".git" || relativePath.includes("/.git/") || relativePath.includes("/node_modules/") || ignoreFilter.ignores(relativePath) || ignoreFilter.ignores(ignorePath);
|
|
15787
|
+
}
|
|
15788
|
+
async function isDirectory(absolutePath) {
|
|
15789
|
+
try {
|
|
15790
|
+
return (await stat4(absolutePath)).isDirectory();
|
|
15791
|
+
} catch {
|
|
15792
|
+
return false;
|
|
15793
|
+
}
|
|
15794
|
+
}
|
|
15795
|
+
function getScriptKind(file) {
|
|
15796
|
+
return file.endsWith(".tsx") ? ts11.ScriptKind.TSX : ts11.ScriptKind.TS;
|
|
15797
|
+
}
|
|
15798
|
+
function getLine(sourceFile2, node) {
|
|
15799
|
+
return sourceFile2.getLineAndCharacterOfPosition(node.getStart(sourceFile2)).line + 1;
|
|
15800
|
+
}
|
|
15801
|
+
function getRecommendedLineLimit(file) {
|
|
15802
|
+
if (file.endsWith(".service.ts"))
|
|
15803
|
+
return 500;
|
|
15804
|
+
if (file.endsWith(".Template.tsx") || file.endsWith(".Zone.tsx"))
|
|
15805
|
+
return 800;
|
|
15806
|
+
if (file.endsWith(".Util.tsx"))
|
|
15807
|
+
return 1000;
|
|
15808
|
+
return null;
|
|
15809
|
+
}
|
|
15810
|
+
function findPatternLines(content, pattern) {
|
|
15811
|
+
return content.split(/\r?\n/).flatMap((line, index) => pattern.test(line) ? [index + 1] : []);
|
|
15812
|
+
}
|
|
15813
|
+
function toPascalCase(value) {
|
|
15814
|
+
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
15815
|
+
}
|
|
15816
|
+
function toPosix(value) {
|
|
15817
|
+
return value.split(path43.sep).join("/");
|
|
15818
|
+
}
|
|
15819
|
+
function groupBy(items, getKey) {
|
|
15820
|
+
const grouped = new Map;
|
|
15821
|
+
for (const item of items) {
|
|
15822
|
+
const key = getKey(item);
|
|
15823
|
+
grouped.set(key, [...grouped.get(key) ?? [], item]);
|
|
15824
|
+
}
|
|
15825
|
+
return grouped;
|
|
15826
|
+
}
|
|
15827
|
+
function compareWarnings(a, b) {
|
|
15828
|
+
return a.scope.localeCompare(b.scope) || (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || a.rule.localeCompare(b.rule);
|
|
15829
|
+
}
|
|
15080
15830
|
// pkgs/@akanjs/devkit/selectModel.ts
|
|
15081
15831
|
import { select as select5 } from "@inquirer/prompts";
|
|
15082
15832
|
// pkgs/@akanjs/devkit/streamAi.ts
|
|
@@ -16038,7 +16788,7 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
|
|
|
16038
16788
|
import { Logger as Logger16 } from "akanjs/common";
|
|
16039
16789
|
|
|
16040
16790
|
// pkgs/@akanjs/cli/package/package.runner.ts
|
|
16041
|
-
import
|
|
16791
|
+
import path44 from "path";
|
|
16042
16792
|
import { Logger as Logger14 } from "akanjs/common";
|
|
16043
16793
|
var {$: $2 } = globalThis.Bun;
|
|
16044
16794
|
|
|
@@ -16058,11 +16808,11 @@ class PackageRunner extends runner("package") {
|
|
|
16058
16808
|
}
|
|
16059
16809
|
async#getInstalledPackageJson() {
|
|
16060
16810
|
const packageJsonCandidates = [
|
|
16061
|
-
`${
|
|
16811
|
+
`${path44.dirname(Bun.main)}/package.json`,
|
|
16062
16812
|
`${process.cwd()}/node_modules/akanjs/package.json`
|
|
16063
16813
|
];
|
|
16064
16814
|
try {
|
|
16065
|
-
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json",
|
|
16815
|
+
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json", path44.dirname(Bun.main)));
|
|
16066
16816
|
} catch {}
|
|
16067
16817
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
16068
16818
|
if (!await Bun.file(packageJsonPath).exists())
|
|
@@ -16071,7 +16821,7 @@ class PackageRunner extends runner("package") {
|
|
|
16071
16821
|
if (packageJson.name === "akanjs" || packageJson.name === "@akanjs/cli")
|
|
16072
16822
|
return packageJson;
|
|
16073
16823
|
}
|
|
16074
|
-
throw new Error(`[package] failed to locate akanjs package.json from ${
|
|
16824
|
+
throw new Error(`[package] failed to locate akanjs package.json from ${path44.dirname(Bun.main)}`);
|
|
16075
16825
|
}
|
|
16076
16826
|
async createPackage(workspace, pkgName) {
|
|
16077
16827
|
await workspace.applyTemplate({ basePath: `pkgs/${pkgName}`, template: "pkgRoot", dict: { pkgName } });
|
|
@@ -16246,7 +16996,7 @@ class PackageScript extends script("package", [PackageRunner]) {
|
|
|
16246
16996
|
}
|
|
16247
16997
|
|
|
16248
16998
|
// pkgs/@akanjs/cli/cloud/cloud.runner.ts
|
|
16249
|
-
import
|
|
16999
|
+
import path45 from "path";
|
|
16250
17000
|
import { confirm as confirm4, input as input5, select as select8 } from "@inquirer/prompts";
|
|
16251
17001
|
import { Logger as Logger15, sleep } from "akanjs/common";
|
|
16252
17002
|
import chalk7 from "chalk";
|
|
@@ -16517,7 +17267,7 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16517
17267
|
await Promise.all(akanPkgs.map(async (library) => {
|
|
16518
17268
|
Logger15.info(`Publishing ${library}@${nextVersion} to ${registry ?? "npm"}...`);
|
|
16519
17269
|
await workspace.spawn("npm", ["publish", "--tag", tag, ...this.#getRegistryArgs(registry), ...this.#getLocalRegistryAuthArgs(registry)], {
|
|
16520
|
-
cwd:
|
|
17270
|
+
cwd: path45.join(workspace.workspaceRoot, "dist/pkgs", library),
|
|
16521
17271
|
env: this.#getRegistryEnv(registry),
|
|
16522
17272
|
stdio: "inherit"
|
|
16523
17273
|
});
|
|
@@ -16571,11 +17321,12 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16571
17321
|
async downloadEnv(cloudApi2, workspace, workspaceId) {
|
|
16572
17322
|
await workspace.mkdir("local");
|
|
16573
17323
|
const localPath = await cloudApi2.downloadEnv(workspaceId);
|
|
16574
|
-
|
|
17324
|
+
const relativePath = path45.relative(workspace.workspaceRoot, localPath).split(path45.sep).join("/");
|
|
17325
|
+
await workspace.spawn("tar", ["-xf", relativePath], { cwd: workspace.workspaceRoot });
|
|
16575
17326
|
await workspace.remove(localPath);
|
|
16576
17327
|
}
|
|
16577
17328
|
async uploadEnv(cloudApi2, workspaceId, filePath) {
|
|
16578
|
-
const file = new File([Bun.file(filePath)],
|
|
17329
|
+
const file = new File([Bun.file(filePath)], path45.basename(filePath));
|
|
16579
17330
|
await cloudApi2.uploadEnv(workspaceId, file);
|
|
16580
17331
|
}
|
|
16581
17332
|
async downloadEnvByScp(workspace) {
|
|
@@ -16625,7 +17376,10 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16625
17376
|
...appNames.map((appName) => `apps/${appName}/env`),
|
|
16626
17377
|
...libNames.map((libName) => `libs/${libName}/env`)
|
|
16627
17378
|
];
|
|
16628
|
-
const
|
|
17379
|
+
const defaultEnvFilePaths = (await Promise.all(envDirs.map(async (envDir) => (await workspace.readdir(envDir)).filter((fileName) => envFilePattern.test(fileName)).map((fileName) => `${envDir}/${fileName}`)))).flat();
|
|
17380
|
+
await this.#syncSecretGitignore(workspace, appNames);
|
|
17381
|
+
const customSecretPaths = await this.#gatherCustomSecretFiles(workspace, appNames);
|
|
17382
|
+
const envFilePaths = [...new Set([...defaultEnvFilePaths, ...customSecretPaths])].sort();
|
|
16629
17383
|
await workspace.mkdir("local");
|
|
16630
17384
|
await workspace.remove("local/env.tar");
|
|
16631
17385
|
if (envFilePaths.length === 0)
|
|
@@ -16636,6 +17390,52 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16636
17390
|
Logger15.info(`Archived ${envFilePaths.length} environment files to local/env.tar`);
|
|
16637
17391
|
return { files: envFilePaths, path: "local/env.tar" };
|
|
16638
17392
|
}
|
|
17393
|
+
async#gatherCustomSecretFiles(workspace, appNames) {
|
|
17394
|
+
const secretPaths = await Promise.all(appNames.map(async (appName) => {
|
|
17395
|
+
const config = await AppExecutor.from(workspace, appName).getConfig();
|
|
17396
|
+
const secretGlobs = config.secrets ?? [];
|
|
17397
|
+
if (secretGlobs.length === 0)
|
|
17398
|
+
return [];
|
|
17399
|
+
const appDir = path45.join(workspace.workspaceRoot, "apps", appName);
|
|
17400
|
+
return secretGlobs.flatMap((pattern) => Array.from(new Bun.Glob(pattern).scanSync({ cwd: appDir, onlyFiles: true })).map((match) => `apps/${appName}/${match.split(path45.sep).join("/")}`));
|
|
17401
|
+
}));
|
|
17402
|
+
return secretPaths.flat();
|
|
17403
|
+
}
|
|
17404
|
+
async#syncSecretGitignore(workspace, appNames) {
|
|
17405
|
+
const patterns = (await Promise.all(appNames.map(async (appName) => {
|
|
17406
|
+
const config = await AppExecutor.from(workspace, appName).getConfig();
|
|
17407
|
+
return (config.secrets ?? []).map((pattern) => `apps/${appName}/${pattern.replace(/^\/+/, "")}`);
|
|
17408
|
+
}))).flat();
|
|
17409
|
+
const uniquePatterns = [...new Set(patterns)].sort();
|
|
17410
|
+
const existing = await workspace.exists(".gitignore") ? await workspace.readFile(".gitignore") : "";
|
|
17411
|
+
const nextContent = this.#applySecretGitignoreBlock(existing, uniquePatterns);
|
|
17412
|
+
if (nextContent === existing)
|
|
17413
|
+
return;
|
|
17414
|
+
await workspace.writeFile(".gitignore", nextContent);
|
|
17415
|
+
Logger15.info(uniquePatterns.length ? `Synced ${uniquePatterns.length} secret pattern(s) from akan.config.ts to .gitignore` : "Removed managed secret patterns from .gitignore");
|
|
17416
|
+
}
|
|
17417
|
+
#applySecretGitignoreBlock(content, patterns) {
|
|
17418
|
+
const beginMarker = "# akan:secrets (managed by akan.config.ts \u2014 do not edit)";
|
|
17419
|
+
const endMarker = "# akan:secrets:end";
|
|
17420
|
+
const lines = content.split(`
|
|
17421
|
+
`);
|
|
17422
|
+
const beginIdx = lines.indexOf(beginMarker);
|
|
17423
|
+
const endIdx = lines.indexOf(endMarker);
|
|
17424
|
+
const stripped = beginIdx !== -1 && endIdx !== -1 && endIdx >= beginIdx ? [...lines.slice(0, beginIdx), ...lines.slice(endIdx + 1)] : [...lines];
|
|
17425
|
+
while (stripped.length && stripped[stripped.length - 1]?.trim() === "")
|
|
17426
|
+
stripped.pop();
|
|
17427
|
+
if (patterns.length === 0)
|
|
17428
|
+
return stripped.length ? `${stripped.join(`
|
|
17429
|
+
`)}
|
|
17430
|
+
` : "";
|
|
17431
|
+
const body = stripped.length ? `${stripped.join(`
|
|
17432
|
+
`)}
|
|
17433
|
+
|
|
17434
|
+
` : "";
|
|
17435
|
+
return `${body}${[beginMarker, ...patterns, endMarker].join(`
|
|
17436
|
+
`)}
|
|
17437
|
+
`;
|
|
17438
|
+
}
|
|
16639
17439
|
}
|
|
16640
17440
|
|
|
16641
17441
|
// pkgs/@akanjs/cli/cloud/cloud.script.ts
|
|
@@ -16667,14 +17467,14 @@ class CloudScript extends script("cloud", [CloudRunner, ApplicationScript, Packa
|
|
|
16667
17467
|
}
|
|
16668
17468
|
async uploadEnv(workspace) {
|
|
16669
17469
|
const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
|
|
16670
|
-
const { path:
|
|
17470
|
+
const { path: path46 } = await this.cloudRunner.gatherEnvFiles(workspace);
|
|
16671
17471
|
if (workspaceId) {
|
|
16672
17472
|
await this.login(workspace);
|
|
16673
17473
|
const cloudApi2 = await CloudApi.fromHost(workspace);
|
|
16674
|
-
await this.cloudRunner.uploadEnv(cloudApi2, workspaceId,
|
|
17474
|
+
await this.cloudRunner.uploadEnv(cloudApi2, workspaceId, path46);
|
|
16675
17475
|
return;
|
|
16676
17476
|
}
|
|
16677
|
-
await this.cloudRunner.uploadEnvByScp(workspace,
|
|
17477
|
+
await this.cloudRunner.uploadEnvByScp(workspace, path46);
|
|
16678
17478
|
}
|
|
16679
17479
|
async deployAkan(workspace, { test = true, registryUrl } = {}) {
|
|
16680
17480
|
const akanPkgs = await this.cloudRunner.getAkanPkgs(workspace);
|
|
@@ -16915,8 +17715,8 @@ class RepairRunner extends runner("repair") {
|
|
|
16915
17715
|
}
|
|
16916
17716
|
|
|
16917
17717
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
16918
|
-
import { mkdir as mkdir12, readFile as
|
|
16919
|
-
import
|
|
17718
|
+
import { mkdir as mkdir12, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
17719
|
+
import path46 from "path";
|
|
16920
17720
|
import { capitalize as capitalize8 } from "akanjs/common";
|
|
16921
17721
|
|
|
16922
17722
|
// pkgs/@akanjs/cli/workflows/shared.ts
|
|
@@ -17459,7 +18259,7 @@ var workflowSpecs = [
|
|
|
17459
18259
|
];
|
|
17460
18260
|
|
|
17461
18261
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
17462
|
-
var resolvePath = (filePath) =>
|
|
18262
|
+
var resolvePath = (filePath) => path46.isAbsolute(filePath) ? filePath : path46.join(process.cwd(), filePath);
|
|
17463
18263
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17464
18264
|
var isWorkflowPlan2 = (value) => {
|
|
17465
18265
|
if (!isRecord3(value))
|
|
@@ -17557,7 +18357,7 @@ var defaultValidationExecutor = (workspace) => async (command3) => {
|
|
|
17557
18357
|
};
|
|
17558
18358
|
}
|
|
17559
18359
|
};
|
|
17560
|
-
var readJsonFile = async (filePath) => JSON.parse(await
|
|
18360
|
+
var readJsonFile = async (filePath) => JSON.parse(await readFile4(resolvePath(filePath), "utf8"));
|
|
17561
18361
|
var planInputString2 = (plan2, key) => {
|
|
17562
18362
|
const value = plan2.inputs[key];
|
|
17563
18363
|
return typeof value === "string" ? value : "";
|
|
@@ -17653,8 +18453,8 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17653
18453
|
const plan2 = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
|
|
17654
18454
|
if (out) {
|
|
17655
18455
|
const outPath = resolvePath(out);
|
|
17656
|
-
await mkdir12(
|
|
17657
|
-
await
|
|
18456
|
+
await mkdir12(path46.dirname(outPath), { recursive: true });
|
|
18457
|
+
await writeFile3(outPath, jsonText(plan2));
|
|
17658
18458
|
}
|
|
17659
18459
|
return format === "json" ? jsonText(plan2) : renderWorkflowPlan(plan2);
|
|
17660
18460
|
}
|
|
@@ -17672,7 +18472,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17672
18472
|
};
|
|
17673
18473
|
let plan2;
|
|
17674
18474
|
try {
|
|
17675
|
-
const parsed = JSON.parse(await
|
|
18475
|
+
const parsed = JSON.parse(await readFile4(resolvePath(planPath), "utf8"));
|
|
17676
18476
|
if (!isWorkflowPlan2(parsed)) {
|
|
17677
18477
|
const report = failedApplyReport("unknown", [
|
|
17678
18478
|
{
|
|
@@ -19477,14 +20277,14 @@ class GuidelinePrompt extends Prompter {
|
|
|
19477
20277
|
async#getScanFilePaths(matchPattern, { avoidDirs = ["node_modules", ".next"], filterText } = {}) {
|
|
19478
20278
|
const glob = new Bun.Glob(matchPattern);
|
|
19479
20279
|
const paths = [];
|
|
19480
|
-
for await (const
|
|
19481
|
-
if (avoidDirs.some((dir) =>
|
|
20280
|
+
for await (const path47 of glob.scan({ cwd: this.workspace.workspaceRoot, absolute: true })) {
|
|
20281
|
+
if (avoidDirs.some((dir) => path47.includes(dir)))
|
|
19482
20282
|
continue;
|
|
19483
|
-
const fileContent = await FileSys.readText(
|
|
20283
|
+
const fileContent = await FileSys.readText(path47);
|
|
19484
20284
|
const textFilter = filterText ? new RegExp(filterText) : null;
|
|
19485
20285
|
if (filterText && !textFilter?.test(fileContent))
|
|
19486
20286
|
continue;
|
|
19487
|
-
paths.push(
|
|
20287
|
+
paths.push(path47);
|
|
19488
20288
|
}
|
|
19489
20289
|
return paths;
|
|
19490
20290
|
}
|
|
@@ -19804,7 +20604,7 @@ class LibraryCommand extends command("library", [LibraryScript], ({ public: targ
|
|
|
19804
20604
|
|
|
19805
20605
|
// pkgs/@akanjs/cli/localRegistry/localRegistry.runner.ts
|
|
19806
20606
|
import { mkdir as mkdir13, rm as rm6 } from "fs/promises";
|
|
19807
|
-
import
|
|
20607
|
+
import path47 from "path";
|
|
19808
20608
|
import { Logger as Logger19 } from "akanjs/common";
|
|
19809
20609
|
var defaultLocalRegistryUrl = "http://127.0.0.1:4873";
|
|
19810
20610
|
var containerName = "akan-verdaccio";
|
|
@@ -19822,8 +20622,8 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19822
20622
|
Logger19.info(`Local registry is already running at ${registry}`);
|
|
19823
20623
|
return registry;
|
|
19824
20624
|
} catch {}
|
|
19825
|
-
const configPath2 =
|
|
19826
|
-
const storagePath =
|
|
20625
|
+
const configPath2 = path47.join(workspace.workspaceRoot, "pkgs/@akanjs/cli/localRegistry/verdaccio.yaml");
|
|
20626
|
+
const storagePath = path47.join(workspace.workspaceRoot, ".akan/verdaccio/storage");
|
|
19827
20627
|
await mkdir13(storagePath, { recursive: true });
|
|
19828
20628
|
await workspace.spawn("docker", [
|
|
19829
20629
|
"run",
|
|
@@ -19846,13 +20646,13 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19846
20646
|
try {
|
|
19847
20647
|
await workspace.spawn("docker", ["rm", "-f", containerName], { stdio: "inherit" });
|
|
19848
20648
|
} catch {}
|
|
19849
|
-
await rm6(
|
|
20649
|
+
await rm6(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
|
|
19850
20650
|
Logger19.info("Local registry storage has been reset");
|
|
19851
20651
|
}
|
|
19852
20652
|
async smoke(workspace, { registryUrl } = {}) {
|
|
19853
20653
|
const registry = this.getRegistryUrl(registryUrl);
|
|
19854
|
-
const smokeRoot =
|
|
19855
|
-
await rm6(
|
|
20654
|
+
const smokeRoot = path47.join(workspace.workspaceRoot, ".akan/e2e");
|
|
20655
|
+
await rm6(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
|
|
19856
20656
|
await workspace.spawn(process.execPath, [
|
|
19857
20657
|
"dist/pkgs/create-akan-workspace/index.js",
|
|
19858
20658
|
smokeRepoName,
|
|
@@ -19869,12 +20669,12 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19869
20669
|
stdio: "inherit"
|
|
19870
20670
|
});
|
|
19871
20671
|
await workspace.spawn("akan", ["typecheck", smokeAppName], {
|
|
19872
|
-
cwd:
|
|
20672
|
+
cwd: path47.join(smokeRoot, smokeRepoName),
|
|
19873
20673
|
env: { ...process.env, AKAN_NPM_REGISTRY: registry, NPM_CONFIG_REGISTRY: registry },
|
|
19874
20674
|
stdio: "inherit"
|
|
19875
20675
|
});
|
|
19876
20676
|
await workspace.spawn("akan", ["build", smokeAppName], {
|
|
19877
|
-
cwd:
|
|
20677
|
+
cwd: path47.join(smokeRoot, smokeRepoName),
|
|
19878
20678
|
env: { ...process.env, AKAN_NPM_REGISTRY: registry, NPM_CONFIG_REGISTRY: registry },
|
|
19879
20679
|
stdio: "inherit"
|
|
19880
20680
|
});
|
|
@@ -20053,8 +20853,46 @@ class PrimitiveCommand extends command("primitive", [PrimitiveScript], ({ public
|
|
|
20053
20853
|
})) {
|
|
20054
20854
|
}
|
|
20055
20855
|
|
|
20056
|
-
// pkgs/@akanjs/cli/
|
|
20856
|
+
// pkgs/@akanjs/cli/quality/quality.script.ts
|
|
20057
20857
|
import { Logger as Logger22 } from "akanjs/common";
|
|
20858
|
+
|
|
20859
|
+
// pkgs/@akanjs/cli/quality/quality.runner.ts
|
|
20860
|
+
class QualityRunner extends runner("quality") {
|
|
20861
|
+
async scan(workspace) {
|
|
20862
|
+
return await new AkanQualityScanner().scan(workspace.workspaceRoot);
|
|
20863
|
+
}
|
|
20864
|
+
}
|
|
20865
|
+
|
|
20866
|
+
// pkgs/@akanjs/cli/quality/quality.script.ts
|
|
20867
|
+
class QualityScript extends script("quality", [QualityRunner]) {
|
|
20868
|
+
async scan(workspace, format = "text") {
|
|
20869
|
+
const spinner2 = workspace.spinning("Scanning Akan code quality...");
|
|
20870
|
+
const result = await this.qualityRunner.scan(workspace);
|
|
20871
|
+
spinner2.succeed(`Quality scan completed (${result.scannedFiles} files, ${result.warnings.length} warnings)`);
|
|
20872
|
+
Logger22.rawLog(format === "json" ? JSON.stringify(result, null, 2) : formatQualityScanResult(result));
|
|
20873
|
+
}
|
|
20874
|
+
}
|
|
20875
|
+
|
|
20876
|
+
// pkgs/@akanjs/cli/quality/quality.command.ts
|
|
20877
|
+
class QualityCommand extends command("quality", [QualityScript], ({ public: target }) => ({
|
|
20878
|
+
quality: target({ desc: "Scan apps and libs for Akan code quality warnings" }).arg("action", String, {
|
|
20879
|
+
desc: "quality action",
|
|
20880
|
+
default: "scan",
|
|
20881
|
+
enum: ["scan"]
|
|
20882
|
+
}).option("format", String, {
|
|
20883
|
+
desc: "output format",
|
|
20884
|
+
default: "text",
|
|
20885
|
+
enum: ["text", "json"]
|
|
20886
|
+
}).with(Workspace).exec(async function(action, format, workspace) {
|
|
20887
|
+
if (action !== "scan")
|
|
20888
|
+
throw new Error(`Unknown quality action: ${action}. Use "scan".`);
|
|
20889
|
+
await this.qualityScript.scan(workspace, format);
|
|
20890
|
+
})
|
|
20891
|
+
})) {
|
|
20892
|
+
}
|
|
20893
|
+
|
|
20894
|
+
// pkgs/@akanjs/cli/repair/repair.script.ts
|
|
20895
|
+
import { Logger as Logger23 } from "akanjs/common";
|
|
20058
20896
|
class RepairScript extends script("repair", [RepairRunner]) {
|
|
20059
20897
|
async repair(kind, {
|
|
20060
20898
|
workspace,
|
|
@@ -20063,7 +20901,7 @@ class RepairScript extends script("repair", [RepairRunner]) {
|
|
|
20063
20901
|
target = null,
|
|
20064
20902
|
format = "markdown"
|
|
20065
20903
|
}) {
|
|
20066
|
-
|
|
20904
|
+
Logger23.rawLog(await this.repairRunner.repair(kind, {
|
|
20067
20905
|
workspace,
|
|
20068
20906
|
app,
|
|
20069
20907
|
module,
|
|
@@ -20103,12 +20941,12 @@ class RepairCommand extends command("repair", [RepairScript], ({ public: target
|
|
|
20103
20941
|
}
|
|
20104
20942
|
|
|
20105
20943
|
// pkgs/@akanjs/cli/scalar/scalar.command.ts
|
|
20106
|
-
import { Logger as
|
|
20944
|
+
import { Logger as Logger24, lowerlize as lowerlize3 } from "akanjs/common";
|
|
20107
20945
|
class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target }) => ({
|
|
20108
20946
|
createScalar: target({ desc: "Create a new scalar type (simple data model without DB)" }).arg("scalarName", String, { desc: "name of scalar" }).with(Sys).option("ai", Boolean, { default: false, desc: "use ai to create scalar" }).option("format", String, { flag: "o", desc: "output format", default: "markdown", enum: ["markdown", "json"] }).exec(async function(scalarName, sys3, ai, format) {
|
|
20109
20947
|
const name = lowerlize3(scalarName.replace(/ /g, ""));
|
|
20110
20948
|
const report = ai ? await this.scalarScript.createScalarWithAi(sys3, name) : await this.scalarScript.createScalar(sys3, name);
|
|
20111
|
-
|
|
20949
|
+
Logger24.rawLog(renderPrimitiveReport(report, format));
|
|
20112
20950
|
}),
|
|
20113
20951
|
removeScalar: target({ desc: "Remove a scalar type from an app or library" }).arg("scalarName", String, { desc: "name of scalar" }).with(Sys).exec(async function(scalarName, sys3) {
|
|
20114
20952
|
await this.scalarScript.removeScalar(sys3, scalarName);
|
|
@@ -20117,7 +20955,7 @@ class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target
|
|
|
20117
20955
|
}
|
|
20118
20956
|
|
|
20119
20957
|
// pkgs/@akanjs/cli/workflow/workflow.script.ts
|
|
20120
|
-
import { Logger as
|
|
20958
|
+
import { Logger as Logger25 } from "akanjs/common";
|
|
20121
20959
|
class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, ScalarScript, PrimitiveScript]) {
|
|
20122
20960
|
async workflow(action, workflow2, inputs, {
|
|
20123
20961
|
format = "markdown",
|
|
@@ -20126,23 +20964,23 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
|
|
|
20126
20964
|
workspace
|
|
20127
20965
|
} = {}) {
|
|
20128
20966
|
if (action === "list") {
|
|
20129
|
-
|
|
20967
|
+
Logger25.rawLog(this.workflowRunner.list({ format }));
|
|
20130
20968
|
return;
|
|
20131
20969
|
}
|
|
20132
20970
|
if (!workflow2)
|
|
20133
20971
|
throw new Error(`Workflow name is required for "${action}".`);
|
|
20134
20972
|
if (action === "explain") {
|
|
20135
|
-
|
|
20973
|
+
Logger25.rawLog(this.workflowRunner.explain(workflow2, { format }));
|
|
20136
20974
|
return;
|
|
20137
20975
|
}
|
|
20138
20976
|
if (action === "plan") {
|
|
20139
|
-
|
|
20977
|
+
Logger25.rawLog(await this.workflowRunner.plan(workflow2, inputs, { format, out }));
|
|
20140
20978
|
return;
|
|
20141
20979
|
}
|
|
20142
20980
|
if (action === "apply") {
|
|
20143
20981
|
if (!workspace)
|
|
20144
20982
|
throw new Error("Workspace is required for workflow apply.");
|
|
20145
|
-
|
|
20983
|
+
Logger25.rawLog(await this.workflowRunner.apply(workflow2, {
|
|
20146
20984
|
dryRun,
|
|
20147
20985
|
format,
|
|
20148
20986
|
workspace,
|
|
@@ -20160,13 +20998,13 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
|
|
|
20160
20998
|
if (action === "validate") {
|
|
20161
20999
|
if (!workspace)
|
|
20162
21000
|
throw new Error("Workspace is required for workflow validate.");
|
|
20163
|
-
|
|
21001
|
+
Logger25.rawLog(await this.workflowRunner.validate(workflow2, { format, workspace }));
|
|
20164
21002
|
return;
|
|
20165
21003
|
}
|
|
20166
21004
|
if (action === "report") {
|
|
20167
21005
|
if (!workspace)
|
|
20168
21006
|
throw new Error("Workspace is required for workflow report.");
|
|
20169
|
-
|
|
21007
|
+
Logger25.rawLog(await this.workflowRunner.report(workflow2, { format, workspace }));
|
|
20170
21008
|
return;
|
|
20171
21009
|
}
|
|
20172
21010
|
throw new Error(`Unknown workflow action: ${action}. Use list, explain, plan, apply, validate, or report.`);
|
|
@@ -20190,11 +21028,11 @@ class WorkflowCommand extends command("workflow", [WorkflowScript], ({ public: t
|
|
|
20190
21028
|
}
|
|
20191
21029
|
|
|
20192
21030
|
// pkgs/@akanjs/cli/workspace/workspace.script.ts
|
|
20193
|
-
import
|
|
20194
|
-
import { Logger as
|
|
21031
|
+
import path49 from "path";
|
|
21032
|
+
import { Logger as Logger26 } from "akanjs/common";
|
|
20195
21033
|
|
|
20196
21034
|
// pkgs/@akanjs/cli/workspace/workspace.runner.ts
|
|
20197
|
-
import
|
|
21035
|
+
import path48 from "path";
|
|
20198
21036
|
var defaultWorkspacePeerDependencies = new Set([
|
|
20199
21037
|
"@react-spring/web",
|
|
20200
21038
|
"@use-gesture/react",
|
|
@@ -20225,6 +21063,12 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20225
21063
|
dict,
|
|
20226
21064
|
overwrite
|
|
20227
21065
|
});
|
|
21066
|
+
created.push(...await workspace.applyTemplate({
|
|
21067
|
+
basePath: ".",
|
|
21068
|
+
template: "workspaceRoot/CLAUDE.md.template",
|
|
21069
|
+
dict,
|
|
21070
|
+
overwrite
|
|
21071
|
+
}));
|
|
20228
21072
|
if (!cursorRules)
|
|
20229
21073
|
return created;
|
|
20230
21074
|
return [
|
|
@@ -20245,7 +21089,7 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20245
21089
|
owner = ""
|
|
20246
21090
|
}) {
|
|
20247
21091
|
const cwdPath = process.cwd();
|
|
20248
|
-
const workspaceRoot =
|
|
21092
|
+
const workspaceRoot = path48.join(cwdPath, dirname2, repoName);
|
|
20249
21093
|
const normalizedRegistryUrl = registryUrl ? getNpmRegistryUrl(registryUrl) : undefined;
|
|
20250
21094
|
const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
|
|
20251
21095
|
const templateSpinner = workspace.spinning(`Creating workspace template files in ${dirname2}/${repoName}...`);
|
|
@@ -20301,9 +21145,9 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20301
21145
|
}
|
|
20302
21146
|
async#getCliPackageJson() {
|
|
20303
21147
|
const packageJsonCandidates = [
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
21148
|
+
path48.join(import.meta.dir, "../package.json"),
|
|
21149
|
+
path48.join(import.meta.dir, "package.json"),
|
|
21150
|
+
path48.join(path48.dirname(Bun.main), "package.json")
|
|
20307
21151
|
];
|
|
20308
21152
|
try {
|
|
20309
21153
|
packageJsonCandidates.unshift(Bun.resolveSync("@akanjs/cli/package.json", import.meta.dir));
|
|
@@ -20319,9 +21163,9 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20319
21163
|
}
|
|
20320
21164
|
async#getAkanPackageJson() {
|
|
20321
21165
|
const packageJsonCandidates = [
|
|
20322
|
-
|
|
20323
|
-
|
|
20324
|
-
|
|
21166
|
+
path48.join(import.meta.dir, "../../../akanjs/package.json"),
|
|
21167
|
+
path48.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
21168
|
+
path48.join(path48.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
20325
21169
|
];
|
|
20326
21170
|
try {
|
|
20327
21171
|
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json", import.meta.dir));
|
|
@@ -20335,13 +21179,13 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20335
21179
|
}
|
|
20336
21180
|
let current = import.meta.dir;
|
|
20337
21181
|
for (let depth = 0;depth < 6; depth++) {
|
|
20338
|
-
const packageJsonPath =
|
|
21182
|
+
const packageJsonPath = path48.join(current, "package.json");
|
|
20339
21183
|
if (await Bun.file(packageJsonPath).exists()) {
|
|
20340
21184
|
const packageJson = await FileSys.readJson(packageJsonPath);
|
|
20341
21185
|
if (packageJson.name === "akanjs")
|
|
20342
21186
|
return packageJson;
|
|
20343
21187
|
}
|
|
20344
|
-
const parent =
|
|
21188
|
+
const parent = path48.dirname(current);
|
|
20345
21189
|
if (parent === current)
|
|
20346
21190
|
break;
|
|
20347
21191
|
current = parent;
|
|
@@ -20418,11 +21262,11 @@ class WorkspaceScript extends script("workspace", [
|
|
|
20418
21262
|
} catch (_) {
|
|
20419
21263
|
gitSpinner.fail("Git repository initialization failed. It's not fatal, you can commit manually");
|
|
20420
21264
|
}
|
|
20421
|
-
const workspacePath2 =
|
|
20422
|
-
|
|
21265
|
+
const workspacePath2 = path49.join(dirname2, repoName);
|
|
21266
|
+
Logger26.rawLog(`
|
|
20423
21267
|
\uD83C\uDF89 Welcome aboard! Workspace created in ${dirname2}/${repoName}`);
|
|
20424
|
-
|
|
20425
|
-
|
|
21268
|
+
Logger26.rawLog(`\uD83D\uDE80 Run \`cd ${workspacePath2} && akan start ${appName}\` to start the development server.`);
|
|
21269
|
+
Logger26.rawLog(`
|
|
20426
21270
|
\uD83D\uDC4B Happy coding!`);
|
|
20427
21271
|
}
|
|
20428
21272
|
async generateAgentRules(workspace, { overwrite = false, cursorRules = true } = {}) {
|
|
@@ -20519,7 +21363,9 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
|
|
|
20519
21363
|
...registry ? { registryUrl: registry } : {}
|
|
20520
21364
|
});
|
|
20521
21365
|
}),
|
|
20522
|
-
generateAgentRules: target({
|
|
21366
|
+
generateAgentRules: target({
|
|
21367
|
+
desc: "Generate AGENTS.md, CLAUDE.md, and optional Cursor rules for Akan coding agents"
|
|
21368
|
+
}).option("overwrite", Boolean, { desc: "Overwrite existing agent rule files", default: false }).option("cursorRules", Boolean, { desc: "Generate .cursor/rules/akan.mdc", default: true }).with(Workspace).exec(async function(overwrite, cursorRules, workspace) {
|
|
20523
21369
|
await this.workspaceScript.generateAgentRules(workspace, { overwrite, cursorRules });
|
|
20524
21370
|
}),
|
|
20525
21371
|
lint: target({ desc: "Lint and fix code in a specific app/lib/pkg" }).with(Exec).option("fix", Boolean, { default: true }).with(Workspace).exec(async function(exec2, fix, workspace) {
|
|
@@ -20538,4 +21384,4 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
|
|
|
20538
21384
|
}
|
|
20539
21385
|
|
|
20540
21386
|
// pkgs/@akanjs/cli/index.ts
|
|
20541
|
-
runCommands(WorkspaceCommand, AgentCommand, ApplicationCommand, LibraryCommand, LocalRegistryCommand, PackageCommand, ModuleCommand, PageCommand, ContextCommand, CloudCommand, GuidelineCommand, ScalarCommand, PrimitiveCommand, RepairCommand, WorkflowCommand);
|
|
21387
|
+
runCommands(WorkspaceCommand, AgentCommand, ApplicationCommand, LibraryCommand, LocalRegistryCommand, PackageCommand, ModuleCommand, PageCommand, ContextCommand, CloudCommand, GuidelineCommand, ScalarCommand, PrimitiveCommand, QualityCommand, RepairCommand, WorkflowCommand);
|