@akanjs/devkit 2.3.11 → 2.3.12-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -264,6 +264,8 @@ describe("AkanAppConfig", () => {
|
|
|
264
264
|
`postgres@${runtimeDependencies.postgres}`,
|
|
265
265
|
`protobufjs@${runtimeDependencies.protobufjs}`,
|
|
266
266
|
]);
|
|
267
|
+
expect(config.getMobileRuntimePackages()).toEqual(["firebase"]);
|
|
268
|
+
expect(config.getMissingMobileDependencySpecs()).toEqual([`firebase@${runtimeDependencies.firebase}`]);
|
|
267
269
|
});
|
|
268
270
|
|
|
269
271
|
test("normalizes multiple mobile targets and validates base paths", () => {
|
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -51,6 +51,9 @@ const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] a
|
|
|
51
51
|
const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
|
|
52
52
|
const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
|
|
53
53
|
const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
|
|
54
|
+
// Native-only client packages that are not bundled into the base bootstrap; installed
|
|
55
|
+
// on demand when a mobile target is built or started (e.g. firebase for push tokens).
|
|
56
|
+
const MOBILE_RUNTIME_PACKAGES = ["firebase"] as const;
|
|
54
57
|
const DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
55
58
|
single: [],
|
|
56
59
|
multiple: ["@libsql/client", "bullmq", "ioredis", "protobufjs"],
|
|
@@ -60,6 +63,7 @@ const AKAN_RUNTIME_PACKAGES = new Set<string>([
|
|
|
60
63
|
...SSR_RUNTIME_PACKAGES,
|
|
61
64
|
...NATIVE_RUNTIME_PACKAGES,
|
|
62
65
|
...DEFAULT_BACKEND_RUNTIME_PACKAGES,
|
|
66
|
+
...MOBILE_RUNTIME_PACKAGES,
|
|
63
67
|
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
|
|
64
68
|
]);
|
|
65
69
|
const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
|
|
@@ -360,11 +364,20 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
360
364
|
return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
|
|
361
365
|
}
|
|
362
366
|
getMissingDatabaseModeDependencySpecs(databaseMode: DatabaseMode = this.defaultDatabaseMode) {
|
|
367
|
+
return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
|
|
368
|
+
}
|
|
369
|
+
getMobileRuntimePackages() {
|
|
370
|
+
return [...MOBILE_RUNTIME_PACKAGES];
|
|
371
|
+
}
|
|
372
|
+
getMissingMobileDependencySpecs() {
|
|
373
|
+
return this.#getMissingDependencySpecs(this.getMobileRuntimePackages());
|
|
374
|
+
}
|
|
375
|
+
#getMissingDependencySpecs(libs: readonly string[]) {
|
|
363
376
|
const rootDependencies = {
|
|
364
377
|
...this.rootPackageJson.dependencies,
|
|
365
378
|
...this.rootPackageJson.devDependencies,
|
|
366
379
|
};
|
|
367
|
-
return
|
|
380
|
+
return libs
|
|
368
381
|
.filter((lib) => !rootDependencies[lib])
|
|
369
382
|
.map((lib) => {
|
|
370
383
|
const version = this.#resolveProductionDependencyVersion(lib);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { DevGeneratedIndexSync } from "./devGeneratedIndexSync";
|
|
6
|
+
|
|
7
|
+
const tempRoots: string[] = [];
|
|
8
|
+
|
|
9
|
+
const makeTempRoot = async () => {
|
|
10
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "akan-generated-index-"));
|
|
11
|
+
tempRoots.push(root);
|
|
12
|
+
return root;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const seedFacet = async (root: string, facet: string, { files, dirs }: { files: string[]; dirs: string[] }) => {
|
|
16
|
+
const dir = path.join(root, "libs", "util", facet);
|
|
17
|
+
await mkdir(dir, { recursive: true });
|
|
18
|
+
await Promise.all(files.map((name) => writeFile(path.join(dir, name), "export const x = 1;\n")));
|
|
19
|
+
await Promise.all(dirs.map((name) => mkdir(path.join(dir, name), { recursive: true })));
|
|
20
|
+
return dir;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const barrelFor = async (root: string, facet: string, seed: { files: string[]; dirs: string[]; trigger: string }) => {
|
|
24
|
+
const dir = await seedFacet(root, facet, seed);
|
|
25
|
+
const sync = new DevGeneratedIndexSync({ workspaceRoot: root });
|
|
26
|
+
const result = await sync.syncForBatch([path.join(dir, seed.trigger)]);
|
|
27
|
+
expect(result.errors).toEqual([]);
|
|
28
|
+
return readFile(path.join(dir, "index.ts"), "utf8");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("DevGeneratedIndexSync facet barrels", () => {
|
|
36
|
+
test("camelCase facets export only clean camelCase names", async () => {
|
|
37
|
+
const content = await barrelFor(await makeTempRoot(), "srvkit", {
|
|
38
|
+
files: [
|
|
39
|
+
"aes.ts",
|
|
40
|
+
"cloudflareApi.ts",
|
|
41
|
+
"cloudflareApi.helper.ts", // dotted → skipped
|
|
42
|
+
"pushNotificationServer.type.ts", // dotted → skipped
|
|
43
|
+
"PushNotificationServer.ts", // PascalCase in camel facet → skipped
|
|
44
|
+
"my_snake.ts", // snake_case → skipped
|
|
45
|
+
"kebab-case.ts", // kebab-case → skipped
|
|
46
|
+
],
|
|
47
|
+
dirs: ["storageApi", "BadDir"], // PascalCase dir → skipped
|
|
48
|
+
trigger: "aes.ts",
|
|
49
|
+
});
|
|
50
|
+
expect(content).toBe(`export * from "./aes";\nexport * from "./cloudflareApi";\nexport * from "./storageApi";\n`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("ui facet exports only clean PascalCase names", async () => {
|
|
54
|
+
const content = await barrelFor(await makeTempRoot(), "ui", {
|
|
55
|
+
files: [
|
|
56
|
+
"Globe.tsx",
|
|
57
|
+
"AkanLogo.tsx",
|
|
58
|
+
"Globe_Dynamic.tsx", // underscore → skipped
|
|
59
|
+
"lowerStart.tsx", // camelCase in ui facet → skipped
|
|
60
|
+
],
|
|
61
|
+
dirs: ["Code", "badDir"], // camelCase dir → skipped
|
|
62
|
+
trigger: "Globe.tsx",
|
|
63
|
+
});
|
|
64
|
+
expect(content).toBe(`export * from "./AkanLogo";\nexport * from "./Code";\nexport * from "./Globe";\n`);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -4,6 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
const BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit"]);
|
|
5
5
|
const FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
6
6
|
const FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
7
|
+
// `ui` exports PascalCase names only; `common`/`srvkit`/`webkit` export camelCase names only. Names with
|
|
8
|
+
// dots, underscores, or hyphens (e.g. `foo.helper`, `Globe_Dynamic`, `kebab-case`) match neither and are skipped.
|
|
9
|
+
const FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
10
|
+
const FACET_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
7
11
|
const MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"] as const;
|
|
8
12
|
const SERVICE_UI_TYPES = ["Util", "Zone"] as const;
|
|
9
13
|
const SCALAR_UI_TYPES = ["Template", "Unit"] as const;
|
|
@@ -109,15 +113,17 @@ export class DevGeneratedIndexSync {
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
async #facetContent(dir: string): Promise<string | null> {
|
|
116
|
+
const nameCasePattern = path.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
|
|
112
117
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
113
118
|
const exportNames = entries
|
|
114
119
|
.flatMap((entry) => {
|
|
115
120
|
const name = entry.name;
|
|
116
121
|
if (name.startsWith(".")) return [];
|
|
117
|
-
if (entry.isDirectory()) return [name];
|
|
122
|
+
if (entry.isDirectory()) return nameCasePattern.test(name) ? [name] : [];
|
|
118
123
|
if (!entry.isFile()) return [];
|
|
119
124
|
if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name)) return [];
|
|
120
|
-
|
|
125
|
+
const exportName = name.replace(FACET_SOURCE_FILE_RE, "");
|
|
126
|
+
return nameCasePattern.test(exportName) ? [exportName] : [];
|
|
121
127
|
})
|
|
122
128
|
.sort();
|
|
123
129
|
if (exportNames.length === 0) return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.12-rc.1",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@langchain/openai": "^1.4.6",
|
|
33
33
|
"@tailwindcss/node": "^4.3.0",
|
|
34
34
|
"@trapezedev/project": "^7.1.4",
|
|
35
|
-
"akanjs": "2.3.
|
|
35
|
+
"akanjs": "2.3.12-rc.1",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "5.5.23",
|